Integration · Webhooks

Webhook Signing in 2026

If you send webhooks, signing them is your half of the security contract. Do it well and your consumers can trust the events; do it carelessly and you force them to trust the network.

John Kihiu12 min read

Most webhook security advice is written for consumers, but the provider sets the terms. If you emit webhooks, how you sign them determines whether every downstream consumer can verify authenticity — or whether they are stuck trusting that no one else found the URL. Signing is the provider's responsibility, and a few decisions make it robust.

Sign the raw body

The standard approach is an HMAC: compute HMAC-SHA256 of the exact response body using a secret shared with the consumer, and send it in a header. Sign the raw serialised bytes you are about to transmit — the same bytes the consumer will receive — so their recomputation matches. If your framework re-serialises between signing and sending, the signatures will not line up and every consumer will fail verification.

Python · signing an outbound webhook
import hmac, hashlib, json, time

def sign_and_send(url, event, secret):
    ts = str(int(time.time()))
    body = json.dumps(event, separators=(",", ":")).encode()
    signed_payload = ts.encode() + b"." + body
    sig = hmac.new(secret.encode(), signed_payload, hashlib.sha256).hexdigest()
    headers = {
        "Content-Type": "application/json",
        "X-Webhook-Timestamp": ts,
        "X-Webhook-Signature": sig,   # consumer signs ts + "." + body the same way
    }
    return post(url, data=body, headers=headers)

Include a timestamp in the signed data

Sign a timestamp alongside the body, as in the example above, so consumers can reject stale requests and defeat replay attacks. Signing the body alone lets an attacker re-send a captured valid request forever; binding a timestamp into the signature gives it an expiry. Document the exact string that gets signed — timestamp + "." + body — or consumers will guess wrong.

Design the secret for rotation

Secrets must be rotatable without dropping events. Let a consumer hold two active secrets at once and, during rotation, sign with the new secret while the old one is still accepted — or send two signatures briefly. Give each endpoint its own secret so a leak is contained to one consumer, and never put the secret in the payload or the URL.

Document verification, and provide a reference

The best-signed webhook is useless if consumers cannot verify it. Publish the exact algorithm, the header names, the signed string format, and a working code sample in a couple of languages. Every ambiguity in your docs becomes a consumer that skips verification because they could not get it working.

Good provider-side signing is HMAC over the raw body, a timestamp bound into the signature, per-endpoint secrets with graceful rotation, and documentation clear enough that verification is easy to implement correctly. You cannot force consumers to verify — but you can make verifying the path of least resistance.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.