Request headers
Each delivery carries these headers:Every delivery carries all four
x-pulse-* headers, including test events sent from the dashboard and affiliate.joined events. Test events are recorded as deliveries like any other, and their payload contains an extra note field.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