What this lets you do
Prove that a webhook request really came from Traceten and has not been tampered with or replayed, before your code acts on it. Anyone who discovers your endpoint URL can POST to it; the signature is what makes a delivery trustworthy.The scheme
Every delivery carries two headers:
The signature is computed over the timestamp and the raw request body, joined by a single
.:
secretis the endpoint’s signing secret, shown once at registration (setup guide, step 3). Use it as a raw UTF-8 string; do not hex- or base64-decode it.timestampis the exact string value of theX-Traceten-Signature-Timestampheader.raw_bodyis the exact bytes of the request body, untouched.
If you use a Traceten server SDK, prefer its built-in verifier over hand-rolling
the scheme:
verifyWebhook() in Node and
Python, and VerifyWebhook in
Go. Each does constant-time comparison and replay-window
enforcement for you and returns a typed event. The snippets below are the reference
implementation of the same scheme, for when you are not using an SDK.Verify the raw body, not a re-serialized one
The single most common verification bug: a framework parses the JSON, your code re-serializes it, and the bytes no longer match what Traceten signed (key order, whitespace, and unicode escaping all differ). Always hash the raw bytes as received.- Express: use
express.raw({ type: "application/json" })on the webhook route, notexpress.json(). - Fastify: use a content-type parser with
{ parseAs: "buffer" }for the webhook route. - Flask: use
request.get_data(), notrequest.get_json(). - FastAPI / Starlette: use
await request.body(), not a Pydantic body parameter. - Go
net/http: readr.Bodywithio.ReadAllbefore any JSON decoding.
Node
express.raw):
Python
request.get_data()):
Go
net/http handler (note io.ReadAll before any decoding):
Conformance vector
Traceten publishes one fixed (secret, timestamp, body) → signature vector. Run your implementation against it before going live: if your code reproduces this signature, it is byte-identical to Traceten’s signer.
The vector’s timestamp is intentionally old, so pass its own value as “now” (or an infinite tolerance) when testing:
now; the digest itself must come out to the expected value:
Troubleshooting
Every signature fails. You are almost certainly hashing a re-serialized body. Log the exact bytes you hash and compare them, character by character, with the delivery log’s payload in Settings → Webhooks. Also confirm you prepend the timestamp and the. separator.
The conformance vector passes but real deliveries fail. Your secret is wrong or stale. Secrets are per-endpoint, and rotating invalidates the old one immediately.
Fails only sometimes. Check server clock skew. The timestamp check compares against your clock; more than 300 seconds of skew rejects genuine deliveries. Sync with NTP rather than widening the tolerance.
Signature header is missing. The request did not come from Traceten’s delivery path. Reject it.

