Skip to main content
The scheme is modelled on Stripe’s, with two differences that matter if you adapt a Stripe verifier: the header is X-Kash-Signature, and the timestamp t= is in Unix milliseconds, not seconds.
Always verify signatures on incoming webhook requests. An unsigned or invalid request must be rejected — the only way to know an event truly came from the Kash API is to verify its HMAC.

The signature format

During the secret rotation overlap window the header may carry two v1= entries (current + previous secret). Accept the request if any matches.

The verification algorithm

  1. Read the raw request body as a string. Don’t reparse JSON — the signature is over the exact bytes that were sent.
  2. Parse the header: split on ,, then on =, into a map.
  3. Read t (the timestamp). If |now - t| > 5 minutes, reject — the signature is too old (replay attack).
  4. Compute expected = HMAC_SHA256(secret, "${t}.${rawBody}") and hex-encode it.
  5. For each v1= entry in the header, compare with expected using a constant-time comparator. If any matches, accept.
That is the whole algorithm. Respond 400 to a request that fails verification: the API does not retry a 400.

Reference implementations

TypeScript / Node.js (the SDK does this for you)

kash.webhooks.verifySignature(body, header, secret) returns { valid, reason } without parsing, if you only need the check.

TypeScript / Node.js (no SDK)

Python

Go

Rust

Common mistakes

  • Re-serialising the JSON body before verifying. The signature is over the exact bytes we POSTed. If you parse + re-stringify, whitespace differs and the signature fails. Always verify against the raw body, then parse JSON.
  • Using == instead of constant-time comparison. == short-circuits on the first differing byte and leaks signature bytes via timing. Use crypto.timingSafeEqual (Node), hmac.compare_digest (Python), hmac.Equal (Go), ct_eq (Rust).
  • Skipping the timestamp freshness check. Without t validation, an attacker who captures one signed payload can replay it forever. The 5-minute tolerance handles legitimate clock skew.
  • Ignoring multiple v1= entries. During the rotation overlap window we send two — accept either. A picky verifier rejects valid traffic mid-rotation.

Testing your verifier locally

The CLI can sign a payload the same way the API does (t=<unix-ms>,v1=<hex>) and POST it to your endpoint:
This POSTs your captured (or hand-written) event body, signed, to your endpoint so you can test your handler’s signature path before going live. Pass --timestamp-ms to test your freshness check, and use kash webhooks verify to check a signature offline.

Reporting verification problems

If deliveries fail to verify, report it from the info button in the Kash web app → Feedback → Report a bug, with the raw X-Kash-Signature header, the X-Kash-Event-Id, and your verification code. Never send the secret itself. Most failures come from verifying a re-serialised body instead of the raw bytes.

Next

Retries & Redelivery

What happens when your endpoint fails — and how to manually replay.

Secret Rotation

Rotate without losing a single delivery.