Skip to main content
Every POST to your endpoint includes the X-Keebai-Signature header. If you don’t verify it, anyone who knows your endpoint URL can send fake payloads. Always verify.

Anatomy of the signature

The signed payload is the timestamp and the raw body concatenated with a dot, not the body alone. That stops anyone from taking an old valid body and resending it with a different timestamp.

Verification steps

1

Capture the raw body

Before parsing JSON. If your framework parses automatically, configure it to keep the raw bytes too (in Express, use bodyParser.raw for this route or read req.rawBody).
2

Parse the header

Pull t and v1 out of the X-Keebai-Signature header.
3

Rebuild the signed payload

signedPayload = ${t}.${rawBody}.
4

Compute HMAC-SHA256 with your secret

expected = HMAC-SHA256(secret, signedPayload).toString('hex').
5

Constant-time comparison

Compare expected to v1 using a constant-time comparison (crypto.timingSafeEqual in Node, hmac.compare_digest in Python). Don’t use plain ===: it’s vulnerable to timing attacks.
6

Anti-replay (optional but recommended)

Reject if now - t > 5 minutes. An attacker could capture an old delivery and replay it if your endpoint is public.

Examples

Best practices

Raw body, not parsed

The HMAC is computed over the exact bytes. If your framework parses and reserializes the JSON, whitespace shifts and the signature stops matching.

Constant-time comparison

=== and == leak length information and enable timing attacks. Always use timingSafeEqual / hmac.compare_digest / hash_equals.

Verify BEFORE any side-effect

Don’t process the event, don’t log details, don’t write to your DB until the signature is valid. Return 401 immediately on failure.

Dedup by `event.id`

We retry on 5xx / timeout / 408 / 429. Your endpoint may receive the same id multiple times. Keep a table of processed event_id values (7-day TTL).

Secret rotation

POST /v1/webhooks/:id/rotate-secret (or keebai webhooks rotate <id>) generates a new secret. The old one is invalidated immediately — there is no grace period. Recommended rotation strategy:
1

Configure your endpoint to accept 2 secrets at once

One variable KEEBAI_WEBHOOK_SECRET (current) and another KEEBAI_WEBHOOK_SECRET_NEXT (staged). The handler tries the first one; if it fails, it tries the second.
2

Rotate

keebai webhooks rotate <id>. Paste the new secret into KEEBAI_WEBHOOK_SECRET_NEXT and deploy.
3

Promote

Once you’ve confirmed the new one works, move it to KEEBAI_WEBHOOK_SECRET and remove the old one.
If your setup is simpler and you can accept a few minutes of failures during rotation, you can rotate and deploy in a single pass.

Common errors