Webhook Authentication: HMAC, Tokens, mTLS and IP Allow-listing Compared

A practical guide to webhook authentication: how HMAC signatures, shared-secret tokens, basic auth, mTLS, IP allow-listing and JWT work, their trade-offs, and when to use each to verify incoming webhooks.

A webhook endpoint is a public URL that accepts POST requests and does something consequential — creates orders, deploys code, moves money. If anyone on the internet can forge a request your handler trusts, that's a problem. Webhook authentication is how you prove an incoming request really came from the provider you expect, and that nobody altered it on the way.

There isn't one right method; there are several, with different guarantees and costs. This guide walks through each, compares them, and tells you when to reach for which.

The strategies, from most to least common

1. HMAC signatures (the default for a reason)

The dominant approach. The provider and you share a secret. For every webhook, the provider computes an HMAC (a keyed hash, usually SHA-256) over the raw request body and puts the result in a header — Stripe-Signature, X-Hub-Signature-256 (GitHub), X-Shopify-Hmac-Sha256, and so on. You recompute the HMAC with the same secret and compare.

import hmac, hashlib

def verify(raw_body: bytes, header_sig: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header_sig)  # constant-time!

Why it wins:

  • Proves authenticity and integrity — only someone with the secret could produce the signature, and any change to the body breaks it.
  • The secret never travels on the wire; only the derived signature does.
  • Works over plain HTTPS with no client certs or infrastructure.

Two rules that trip people up: verify against the raw bytes, not a re-serialized JSON object (re-encoding changes bytes and breaks the match), and use a constant-time comparison to avoid timing attacks. Many providers also include a timestamp you should check to reject replays. We cover the mechanics in depth in how to verify a webhook signature, and you can test your logic with the free HMAC generator & verifier.

2. Shared-secret tokens

The simplest scheme: the provider sends a secret value in a header (e.g. Authorization: Bearer <token> or a custom X-Webhook-Token), and you compare it to a known value. Easy to set up, but weaker than HMAC because the secret travels with every request — anyone who captures one request (a logged header, a misconfigured proxy) has your secret forever. It also says nothing about payload integrity. Fine for low-stakes internal webhooks; reach for HMAC when the events matter.

3. Basic auth

Username and password in the Authorization header. Same trade-offs as a shared token — the credential is sent on every request — but it's universally supported and trivial to configure on both ends. Only acceptable over HTTPS. Handy when a provider can't do HMAC but can do basic auth, or for quickly locking down an internal endpoint.

4. mTLS (mutual TLS)

Both sides present X.509 certificates during the TLS handshake; your server only completes the connection if the client's cert is signed by a CA you trust. This authenticates the connection itself, before any HTTP is exchanged — strong, but operationally heavy: you manage certificates, rotation and a CA. Reserved for high-security, often regulated, B2B integrations where both parties control the infrastructure.

5. IP allow-listing

Only accept requests from the provider's published IP ranges. Cheap defense in depth, but not sufficient alone: it doesn't prove payload integrity, provider IP ranges change (breaking you silently), and shared cloud IPs can be reused by others. Best paired with HMAC.

There's a flip side worth knowing: when you are the one calling someone else's allow-listed endpoint, your requests need a predictable source IP. A forwarder with a static outgoing IP gives you one stable address to hand to a partner's firewall, instead of a rotating pool you can't allow-list.

6. JWT

The provider signs a JSON Web Token (typically with HMAC or an RSA/ECDSA key) and sends it in the Authorization header. You verify the signature and the standard claims (exp for expiry, iss for issuer, aud for audience). JWTs carry their own expiry and metadata, which makes them nice for short-lived authorization and for verifying with a public key (so your side never holds a shared secret). The cost is more moving parts than a plain HMAC body signature.

Quick comparison

MethodProves payload integrity?Secret on the wire?Setup costGood for
HMAC signatureYesNoLowThe default — almost everything
Shared tokenNoYesVery lowLow-stakes / internal
Basic authNoYesVery lowLegacy / quick lockdown
mTLSConnection-levelNoHighRegulated B2B
IP allow-listNon/aLowDefense in depth only
JWTYes (token)DependsMediumShort-lived / public-key verify

When to use which

  • Building a new endpoint? Start with HMAC signature verification. It's the best ratio of security to effort and what most providers already speak.
  • Provider can't sign bodies? Use basic auth or a shared token over HTTPS, and add IP allow-listing on top.
  • Money, health, or compliance on the line? Consider mTLS for connection-level guarantees, still with HMAC at the application layer.
  • Need expiring, delegated access or public-key verification? Reach for JWT.
  • Always: layer it. Signature plus IP allow-list plus a timestamp/replay check is meaningfully stronger than any single control.

How Webhook Relay fits in

Webhook Relay sits in front of your services and can enforce authentication on its endpoints — supporting HMAC, JWT and basic auth so unauthenticated traffic is rejected before it ever reaches your code (see the webhook auth docs for the options). On the outbound side, the static outgoing IP gives you a stable address to put on a partner's allow-list, and transformations let you add or rewrite auth headers in flight when a destination expects a different scheme than the source provides.

For the broader picture — replay protection, raw-body verification, HTTPS, and least-privilege handlers — see our webhook security best practices.

The short version

  • HMAC signatures are the default: they prove authenticity and integrity and never expose the secret. Verify the raw body with a constant-time compare.
  • Tokens and basic auth are simple but send the secret every time — fine for low-stakes endpoints over HTTPS.
  • mTLS and JWT cover stronger or more dynamic needs at higher operational cost.
  • IP allow-listing is great defense in depth but never a standalone control.
  • Layer multiple methods, and add a timestamp/replay check.

Test your signature logic now with the HMAC verifier, read how to verify a webhook signature, or create a free account to put authenticated endpoints in front of your services.