Request headers
Each delivery carries these headers:Test events sent from the dashboard carry
x-pulse-id and x-pulse-event but no x-pulse-delivery-id, because no delivery record is created for them. Their payload also contains an extra note field. Real events always carry all four headers.The signing secret
Each Pulse has its own signing secret, prefixedwhsec_, generated by Chariow when the Pulse is created.
To retrieve it: Automations → Pulses → select your Pulse → Overview tab → Signing secret. It is masked by default; use Reveal, Copy and Rotate on that block.
The secret is encrypted at rest and never appears in API listings or request logs — those expose only a masked form such as whsec_••••a1b2. The plaintext value is returned only on an explicit reveal.
Rotating the secret
Rotation takes effect immediately and cannot be undone: the previous secret stops working the moment you rotate. Update your endpoint configuration in the same window. Deliveries dispatched before the rotation keep the signature computed with the previous secret, so adopt the new secret before replaying old deliveries.Signature scheme
x-pulse-id, x-pulse-delivery-id and any timestamp are all excluded from the computation.
Body encoding
The body is compact UTF-8 JSON:- no indentation whitespace and no newlines;
- forward slashes are escaped —
https:\/\/example.com; - non-ASCII characters are escaped as
\uXXXX, so the transmitted body is ASCII in practice; - key order is the order in the transmitted body.
Comparing the signature
Strip thesha256= prefix and compare against the bare hex digest, or rebuild "sha256=" + digest and compare the whole string. Either works as long as you are consistent.
Always compare in constant time (crypto.timingSafeEqual, hash_equals, hmac.compare_digest) and check that both values have the same length first — timingSafeEqual throws on buffers of different sizes.
The prefix identifies the algorithm so the scheme can evolve without breaking existing receivers. Treat any value that does not start with sha256= as a scheme you do not yet support.
Verification examples
Idempotency and replay protection
The signature carries no timestamp, and that is deliberate. It is computed once when the delivery is dispatched and reused unchanged by every retry. A time window would wrongly reject a legitimately delayed retry — the last attempt of a delivery can land nearly three hours after the first. Replay protection is handled byx-pulse-delivery-id instead. That identifier is stable across every attempt of the same delivery, which makes it a ready-made idempotency key:
- Read
x-pulse-delivery-id. - If you have already processed it, return
200and stop. - Otherwise persist it, return
200, and process asynchronously.
x-pulse-delivery-id, so it will pass your idempotency check and be processed again. That is the intended behaviour: replay exists to let you reprocess an event your endpoint missed.
Troubleshooting a signature mismatch
Related resources
Pulses Guide
Events, payloads, delivery history and retries
Best Practices
Security and reliability recommendations