Skip to main content
Every Pulse Chariow sends is signed with a secret that belongs to that Pulse alone. Verifying that signature is the only way to know a request on your endpoint genuinely originates from Chariow — your endpoint URL is public, so anyone who discovers it can post to it. This page is the complete signing contract. Implement it once and you can trust every payload you receive.

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, prefixed whsec_, generated by Chariow when the Pulse is created.
The signing secret is not your API key, and it is not derived from your API key, your store identifier, or any other integration credential. It is an independent value. Using anything else to compute the HMAC will never produce a matching signature.
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

Only the raw body is signed. The HTTP method, the URL, the headers, 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.
Never re-serialise the parsed payload. JSON.stringify(req.body) or json.dumps(payload) will not reproduce these bytes — the escaped forward slashes alone are enough to break the digest. Capture the raw body before any JSON parsing.

Comparing the signature

Strip the sha256= 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.
Reject any request without a valid x-chariow-signature header with a 401.

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 by x-pulse-delivery-id instead. That identifier is stable across every attempt of the same delivery, which makes it a ready-made idempotency key:
  1. Read x-pulse-delivery-id.
  2. If you have already processed it, return 200 and stop.
  3. Otherwise persist it, return 200, and process asynchronously.
Deduplicate on x-pulse-delivery-id, not on the entity identifier inside the payload. A single sale can legitimately produce several deliveries — one per subscribed Pulse, plus any manual replay you trigger yourself.
A manually replayed delivery is a new delivery with its own 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

Use the Deliveries tab on the Pulse detail page to replay a real delivery while you debug. It shows the exact payload sent and the exact response your endpoint returned, which is far more useful than a test event for validating verification end to end.

Pulses Guide

Events, payloads, delivery history and retries

Best Practices

Security and reliability recommendations