Every webhook delivery carries an X-DSS-Signature header. Verify it on every request before doing anything with the body — a missing or invalid signature means the request didn’t come from us (or it’s a replay).

The signature

  • t — Unix timestamp (seconds) of when we signed.
  • v1 — HMAC-SHA256 of <t>.<raw-body-bytes>, encoded as lowercase hex.
The secret is the webhook signing secret we issued at onboarding — separate from your client_secret. Rotate it independently via your DSS account contact.

The verification rules

A verifier must enforce all four:
  1. Parse the header. If t or v1 is missing or malformed, reject with 400.
  2. Reject stale events. If |now - t| > 300 seconds, reject. This is the replay protection window.
  3. Recompute HMAC-SHA256 of f"{t}.".encode() + raw_body_bytes using the signing secret. Compare in constant time against v1.
  4. Reject mismatches. Don’t trust any field of the body until the signature checks.
Use the raw bytes of the request body, not a re-serialised JSON view of it. JSON re-serialisation reorders keys or changes whitespace, both of which break the signature. In FastAPI: await request.body(). In Express: express.raw({ type: "application/json" }) and read req.body as a Buffer.

Python

Copy this verbatim:

FastAPI handler

Flask handler

Tests against the spec

Our canonical fixture:
Your verifier should accept this with header t=1716714840,v1=99d56ccfe6de640971036fc31a8bb476415322e6b687301c96fe15ac81e3fcff only within 5 minutes of the timestamp. If you’re testing offline, freeze the clock at 1716714840.

Node

Express handler

Next.js Route Handler (App Router)

What you must do (checklist)

1

Verify the signature on every request

No verified signature, no body parsing. Return 400.
2

Enforce the 5-minute replay window

Even with a valid signature, reject events older than 300 seconds.
3

Compare in constant time

hmac.compare_digest (Python) or crypto.timingSafeEqual (Node). String == is wrong.
4

Dedupe on event id

At-least-once delivery means you will see duplicates. The id is stable across attempts — store the last N you’ve seen in Redis or your DB.
5

Respond 2xx within 5 seconds

Hand off heavy work to a background queue. Acknowledging fast keeps you out of the retry schedule.
6

Always return 2xx on duplicates

A 4xx on a duplicate looks like a delivery failure to us and triggers pointless retries.

What you must not do

  • Do not trust the body before the signature checks.
  • Do not verify against a re-serialised body. The signature is over the bytes we sent, in the order we sent them.
  • Do not log the signing secret. Treat it like any other long-lived credential.
  • Do not allow http:// for your webhook URL outside local development. We won’t deliver to plain HTTP in production.