POST. Verify the signature on each request
so your handler only acts on deliveries that came from Brudcast.
Before you start: you need the endpoint’s signing secret, which is shown once when you create
the endpoint or rotate its secret. Your framework must also let you read the request body as
raw bytes, before any JSON parsing.
How Brudcast signs a request
X-Webhook-Signature is:
- an HMAC-SHA256
- keyed with the endpoint’s secret, used as-is, as a UTF-8 string
- computed over the raw request body, byte for byte
- written as lowercase hexadecimal, 64 characters, with no prefix such as
sha256=
timestamp, so the signature covers it too.
Verify each request
1
Read the raw body
Capture the exact bytes Brudcast sent. If a JSON parser reads the body first, and you then
serialize it again, key order and spacing can change and the signature won’t match.
2
Compute the expected signature
Calculate HMAC-SHA256 of the raw body, keyed with your secret, and hex-encode it in lowercase.
3
Compare in constant time
Compare your value with
X-Webhook-Signature using a constant-time function. A normal string
comparison stops at the first difference, and the time it takes can leak how much of a forged
signature was right. If the values differ, return 401 and stop.4
Check the timestamp
Parse the body and reject deliveries whose
timestamp is older than your tolerance. See
Choose a tolerance below.5
Skip repeats
Look up
delivery_id in your store. If you’ve already processed it, return 200 and do
nothing. Otherwise, record it along with the event.6
Respond quickly
Return a
2xx once the event is stored, and do the real work afterwards. If your handler takes
longer than the endpoint’s timeout, Brudcast treats the attempt as failed and sends the same
delivery again.Code
Each example reads the secret fromBRUDCAST_WEBHOOK_SECRET and listens on
/webhooks/brudcast.
Choose a timestamp tolerance
timestamp is the time Brudcast recorded the delivery, and it doesn’t change. A retry, or a
resend you trigger by hand, arrives with the original value. A tight window, such as five minutes,
rejects legitimate retries on endpoints with many attempts, and any resend you make later.
Your delivery_id check is what stops a captured request from being replayed. The timestamp
limits how long you need to remember those IDs. Pick a tolerance that covers your endpoint’s retry
schedule, and keep processed IDs for at least that long.
The full schedule is in
Retries and deliveries. The
examples above use 24 hours.
Troubleshooting
The signature never matches
The signature never matches
Why: usually the body was changed before you hashed it. A JSON middleware may have parsed
it, a framework may have decoded it, or a proxy may have re-encoded it. Less often, the secret
is wrong: it has a trailing newline from an environment file, it was hex-decoded, or it’s from
before a rotation.Fix: hash the raw bytes, as in the examples. Print the length of the secret you load: it
should be 64. If you’ve lost the secret, rotate it and update your server.
Deliveries my server rejected with 401 aren't retried
Deliveries my server rejected with 401 aren't retried
Why: Brudcast retries only when it gets no response. A
401 is a response, so the delivery
is marked delivered.Fix: fix the verification, then reprocess what you missed. Open each delivery in Delivery
Logs. Its Request Body holds the event’s data.The same event arrives twice
The same event arrives twice
Why: your server took longer than the endpoint’s timeout, so Brudcast treated the attempt as
failed and tried again with the same body.Fix: skip any
delivery_id you’ve already processed, and return 200 before doing slow
work.Payload reference
The headers and fields you’re verifying.
Create an endpoint
Point an endpoint at your own machine while you build.