Webhook security best practices: a receiver checklist
Accept HTTPS only, verify an HMAC over the raw body in constant time, reject stale timestamps, dedupe on the event id, and answer 2xx fast.

Treat every webhook as untrusted until you verify it. Accept it only on an HTTPS endpoint, check its HMAC signature over the raw body in constant time, reject timestamps outside a short window to stop replays, dedupe on the event id, answer 2xx fast and do the work afterwards, and read anything that matters back from the API.
Sume's deliveries carry what each check needs: a signed timestamp, a stable event id, and one signature per live secret during a rotation. The Sume details come from its Run webhooks, Webhooks, Runs and results and Verifying webhooks docs, read on 2026-09-28.
What should a webhook receiver check?
Six checks, each answering a different attack or failure. The right-hand column is what Sume provides for it:
| Check | Stops | On Sume |
|---|---|---|
| HTTPS-only endpoint | Reading or changing deliveries in transit | Localhost, private ranges, credentials in the URL and plain HTTP are refused at create and checked again at delivery |
| Signature over the raw body | Forged or altered deliveries | HMAC-SHA256 over <timestamp>.<raw_body>, sent as sume-v1=<hex_signature> |
| Timestamp window | Replays of an old, genuine delivery | x-sume-webhook-timestamp; five minutes is a reasonable default |
| Deduplication | Processing one event twice | request_id on run webhooks, job_id on job webhooks |
Fast 2xx | Timeouts that trigger retries | 10 seconds per attempt, up to 10 attempts |
| Confirm with the API | Acting on a payload alone | A run webhook's payload is byte-identical to the run you can read back |
How do I verify a webhook signature correctly?
Four rules make the check hold. In current code, verifyWebhook in Sume's TypeScript SDK handles the last three; the first is up to you. Signed webhooks for Sume video runs shows the SDK handler, and the Python and Go posts write the same checks by hand.
- Verify the raw bytes, before any JSON parse. A parsed-and-reserialized object does not verify, because key order and whitespace are part of what was signed.
- Parse the signature header; never compare it whole. During a secret rotation, Sume sends one
sume-v1=entry per live secret, comma-separated, newest first, and a delivery is valid when any entry matches. Skip entries without thesume-v1=prefix. - Compare in constant time, and compare every entry even after a match, so timing can't reveal which secret matched.
- Refuse an empty secret. An empty key is one anyone can use, so an unset environment variable must fail the check; in current code the SDK returns
falsewhen the secret is empty.
How do I stop webhook replay attacks?
A replay attack resends a genuine, validly signed request that someone captured. The signature can't catch it, because the bytes are real. Two checks do, and one caveat applies:
- Reject stale timestamps. Sume signs
<timestamp>.<raw_body>, so the timestamp can't be changed without breaking the signature. Reject a delivery whosex-sume-webhook-timestampis outside your window; the SDK'stoleranceSecondsdefaults to300. - Dedupe on the event id. A replay inside the window carries the same
request_id(runs) orjob_id(jobs) as the original, and so does each of Sume's own retries. Record the ids you've processed and ignore repeats. - Expect legitimate repeats as well. Sume's Redeliver re-sends a real event with a fresh timestamp and signature, so it passes the window check; the id is what marks it as a repeat.
Are webhooks secure?
As secure as the receiver makes them. A webhook URL is public, so anyone can send it a request; the signature is what proves a delivery came from someone holding the secret. That makes the secret the thing to protect.
Sume derives the signing secret for your workspace, so nobody else's secret verifies a delivery signed for you. Store it the way you store the API key, as in Where to store API keys, and rotate it if it may have leaked; Signed webhooks for Sume video runs covers the rotation window. When a signature won't verify, compare the x-sume-webhook-secret-fingerprint header with the fingerprint in the dashboard rather than pasting the secret anywhere.
What should the endpoint do after it verifies?
- Record the event durably, answer
2xx, then process. A slow endpoint burns the 10-second attempt budget and gets retried. - Answer an unknown event type with
204. Sume's docs say this stops a newly added event type from becoming a500and a retry storm. - Register the final URL. Redirects are not followed, and a
3xxcounts as a failed attempt; Webhook URL rejected as invalid? lists Sume's other URL rules. - Confirm before you act on anything costly. A run webhook's
payloadis byte-identical todatafromGET /v1/format-runs/{run_id}for a Format run, so you can read the run back from the API first. Debug Sume webhook delivery covers deliveries that fail.
Sources
Related posts
More in Developers
- Webhook vs API: what's the difference?
An API call is your code asking a server for something; a webhook is the server calling your URL when something happens. The two work together.
- What is a dead letter queue? DLQs for AI job pipelines
A dead-letter queue holds messages that failed processing too many times, so they stop looping and can be inspected. Which AI job failures go there.
- What is a video API? The five kinds, explained
A video API lets code make, edit or deliver video over HTTP. The five kinds, what each one takes and returns, and how to tell which one you need.
- Edit decision list (EDL): what it is, with an example
An edit decision list (EDL) is the ordered list of edits that rebuilds a cut: source, track, transition, and timecodes. An example and its JSON form.
Written by Sume