Cloud Security

9 Webhook Security Controls Every Team Should Enforce

October 24, 2026 · 6 min read · By CloudAI Security
9 Webhook Security Controls Every Team Should Enforce

Webhook security comes down to one question: can your endpoint prove that an inbound request really came from the provider, and that it was not captured and replayed later? The controls that answer it are cheap to implement and verifiable: set a signing secret on every endpoint, verify an HMAC signature over the raw request body using constant-time comparison, reject deliveries whose timestamp falls outside a small freshness window, restrict what the handler can do, rotate secrets on a schedule, and alert on signature failures. GitHub computes an HMAC hash signature over each payload using your secret token and delivers it in the X-Hub-Signature-256 header of every webhook delivery. Stripe and Slack follow the same pattern with their own headers, so one handler design works across providers.

Verify the HMAC Signature

Every major provider signs webhook deliveries with an HMAC: a hash of the raw request body keyed by a secret shared only between you and the provider. Your handler recomputes the hash and accepts the request only when the two digests match. Three implementation details decide whether this control actually holds. First, verify against the raw request body: frameworks that deserialize and re-serialize JSON before your verification code runs change the bytes and make valid signatures fail, a failure mode Stripe documents explicitly for its own endpoints. Second, fail closed when the signature header is missing instead of treating the request as unauthenticated but acceptable. Third, compare digests safely. GitHub’s documentation warns that a plain == comparison leaks timing information and recommends constant-time comparison functions instead.

GitHub’s validation guide includes worked examples in several languages and a published test vector: a known secret and payload with the digest your implementation must reproduce, which belongs in your unit tests.

Reject Replay Deliveries

A valid signature proves who sent a request, not when. An attacker who captures one legitimate delivery can replay it later unless your handler enforces freshness, so providers sign a timestamp together with the rest of the payload. Stripe includes a signed timestamp in the Stripe-Signature header, and its official verification libraries apply a default tolerance of 5 minutes between that timestamp and the current time. Slack’s verification recipe rejects any request whose timestamp differs from local time by more than five minutes, because the signature itself covers the timestamp. Two operational rules follow. Keep servers clock-synchronized with NTP so legitimate deliveries are never discarded as stale, and never widen the tolerance window to work around clock drift — fix the clock instead. Make processing idempotent as well: retries are a normal part of webhook delivery, and a retried event must not double-charge a customer or duplicate a side effect.

The mechanics are documented in each provider’s primary reference: Stripe’s webhook endpoint guide and Slack’s request verification page.

Tighten the Endpoint

Signature verification is necessary but not sufficient. Treat the webhook receiver as a privileged, internet-exposed service and shrink its blast radius. Give each provider its own endpoint path and its own handler, so compromising one integration cannot reach another. Do not rely on a long or obscure URL as authentication: unguessable paths reduce scanning noise, not attackers who read the URL from your logs, configuration, or referrer headers. Least privilege applies to the handler’s own credentials too. The receiver frequently holds write access to databases, queues, or a provider API, so scope those grants to exactly the operations the handler performs and nothing wider. The vendor side deserves the same discipline: review which third-party applications hold OAuth grants into your SaaS and cloud accounts and revoke the stale ones, using the method described in this OAuth grant audit walkthrough.

Endpoint hygiene extends to DNS. Retired integrations leave behind subdomains that once pointed at a webhook receiver; if those records dangle after the receiver is decommissioned, an attacker can claim the target and capture traffic meant for you. The dangling DNS cleanup procedure covers how to find and eliminate those records before someone else does.

Rotate Secrets Without Downtime

Signing secrets are long-lived credentials, and they should rotate like any other. What makes rotation safe is that verification is a local computation: during a changeover your handler can accept two secrets simultaneously. A provider-independent procedure: overlap, then cut over.

  1. Generate the new secret and store it alongside the current one in your secret manager.
  2. Update the provider configuration so it signs new deliveries with the new secret.
  3. Deploy a handler that verifies against the new secret first and falls back to the old one.
  4. Confirm deliveries verify with the new secret for at least one full provider retry cycle.
  5. Remove the old secret from code, configuration, and the secret manager, and record the rotation date.

Each delivery is signed with exactly one secret, so the fallback order is deterministic and no legitimate delivery is rejected mid-rotation.

Monitor Deliveries and Anomalies

Verification failures are telemetry, not noise. A baseline of near-zero signature failures followed by a spike usually means either a misconfiguration — a rotated secret not fully deployed, a proxy rewriting the request body — or someone probing your endpoint with forged requests. Both deserve an alert. Track three signals per endpoint: delivery success rate from the provider dashboard or your own logs, signature failure count, and the fraction of deliveries arriving older than your tolerance window. Respond to every delivery with a fast 2xx and process the payload asynchronously; slow handlers trigger provider retries, which distorts your failure metrics and keeps signed payloads in flight longer than necessary. Stripe documents this requirement explicitly: return a successful status before any complex logic that could time out.

Webhook Hardening Checklist

Nine controls, in the order you should implement them:

#ControlWhat it stops
1Set a signing secret on every endpointForged deliveries accepted by default
2Verify the HMAC signature before parsing the bodyUnauthenticated payloads reaching business logic
3Compare digests with a constant-time functionTiming attacks against the comparison itself
4Verify against the raw request bodyFalse rejections from re-serialized payloads
5Enforce a timestamp tolerance windowCaptured deliveries replayed later
6Make processing idempotentDuplicated side effects on provider retries
7Scope handler credentials to least privilegeOne endpoint compromise becoming account-wide
8Rotate signing secrets on a scheduleLong-term value of a leaked secret
9Alert on signature failures and anomaliesSilent probing and configuration drift

Sources