Guide 10 min read

Inbound Webhooks Need Signature Checks

Slack, GitHub, Discord, Stripe, and other platforms send high-trust events to public endpoints. Signature verification, replay windows, raw-body handling, and idempotency decide whether a webhook is evidence or attacker input.

By Protocol Report Editorial | Updated July 6, 2026
Technical editorial diagram showing signed webhook events, a public endpoint, signature verification, replay protection, an event queue, and a forged request being blocked
Short Version

A webhook endpoint is usually designed to be reachable from the public internet. That is useful for automation, but it means the endpoint has to treat every inbound request as untrusted until the sender and payload are verified. A secret-looking URL, a familiar JSON shape, or a source IP that looks reasonable is not enough when the request can trigger role changes, deployments, billing actions, incident alerts, invite creation, or moderation workflows.

The practical control is request verification before business logic. Slack signs requests with an app signing secret, GitHub signs deliveries with a webhook secret, Discord requires Ed25519 validation for interaction endpoints, Stripe signs event payloads, and Amazon SNS signs messages for HTTP endpoints. The details differ, but the pattern is consistent: preserve the raw body, validate the signature, reject stale or replayed requests, process each event idempotently, and keep authorization checks above the transport proof.

Key Takeaways

  • check_circle A webhook URL is a routing address, not a complete authentication mechanism.
  • check_circle Signature verification has to run before parsing, queuing, role updates, deploys, billing changes, or message sends.
  • check_circle Raw request bodies matter because many providers sign the exact bytes they sent, not the JSON object after a framework rewrites it.
  • check_circle Replay protection and duplicate handling are separate controls. A valid signed request can still be old or already processed.
  • check_circle A provider signature proves origin and integrity, not whether the user, workspace, repository, guild, or account should be allowed to trigger the action.
  • check_circle Each provider, environment, and high-risk event path should have its own secret, rotation plan, and failure logging.

The Endpoint Is Public By Design

Webhooks exist because one service needs to notify another without a user sitting in front of both systems. Slack sends slash commands and Events API callbacks. GitHub sends repository, organization, and GitHub App events. Discord sends interaction payloads to an application endpoint. Stripe sends payment and subscription events. Amazon SNS can deliver notifications and subscription confirmations to HTTP endpoints. For a community or collaboration product, those requests often sit near the control plane.

That is the security tension. The endpoint has to be reachable enough for the provider to call it, but it may also be powerful enough to grant roles, publish announcements, open incident tickets, start builds, update CRM records, grant paid access, or remove a member. Attackers do not need to break the provider if they can send a plausible request directly to the receiver and the receiver trusts the shape of the payload.

A Secret URL Is Not Enough

Some teams still treat a webhook path as a secret because it contains a long random token. That can help reduce noise, but it is brittle as the only control. URLs appear in reverse-proxy logs, analytics records, crash reports, CI output, screenshots, browser history, documentation, support tickets, and chat messages. They are also easy to reuse by accident across development, staging, and production.

Source IP filtering is useful where a provider maintains stable published ranges, but it is not a substitute for payload authentication. Network controls can be misconfigured, bypassed by trusted infrastructure, or lost behind a proxy. Signature verification binds the payload to a provider-held secret or public key. It gives the receiver a stronger answer to the first question: did this exact message come from the expected sender without modification?

Verify Before Trusting The Payload

Provider implementations vary, but the order of operations should be boring. Capture the raw request body. Read the signature and timestamp headers. Recreate the provider's signed message exactly as documented. Validate the HMAC or public-key signature with the right secret or public key. Compare values with a constant-time comparison where that applies. Reject the request before any side effect if verification fails.

The raw-body requirement is where many otherwise careful integrations fail. A web framework may parse JSON, normalize whitespace, reorder fields, transcode text, or consume the body stream before verification code runs. Stripe explicitly warns that manipulating the raw body breaks verification. Slack signs a basestring that includes the timestamp and raw body. GitHub signs the payload contents. Discord signs the timestamp concatenated with the body. Verification middleware should sit at the edge of the route, before generic body parsing changes the bytes.

Replay Windows And Duplicate Events

A correct signature proves that the request was valid when it was created. It does not prove that the request is fresh. Slack and Stripe both use timestamps as part of replay protection, and Discord includes a timestamp header for interaction verification. Receivers should reject old requests outside a narrow tolerance, keep clocks synchronized, and alert on repeated stale attempts because a captured signed payload can be replayed if the receiver only checks the signature.

Duplicate delivery is a different problem. Providers retry when a receiver times out or returns an error, and operators can manually resend some events. Stripe tells webhook handlers to guard against duplicated event receipts. GitHub has delivery identifiers. The receiver should store event IDs, make actions idempotent, and separate acknowledgement from slow work. A queue is usually safer than doing complex business logic inside the request-response path.

Scope Secrets Like Production Credentials

Webhook signing secrets are production credentials. They should live in a secret manager or protected environment variable, not in source code, test fixtures, or a shared wiki. A single shared secret across providers or environments makes rotation hard and expands the blast radius. Development, staging, and production endpoints should have separate secrets, and high-risk event families may deserve separate endpoints so access can be reviewed and revoked independently.

Rotation has to be rehearsed. Some providers support overlapping secrets for a short period; others require an immediate cutover. The receiver should expose a clear deployment path for accepting a new secret, logging which version verified a request, and removing the old one after the provider stops signing with it. A leaked webhook secret should trigger the same response discipline as a leaked bot token: rotate, inspect logs, replay test critical actions, and look for forged or unusual event patterns.

Signature Is Not Authorization

A valid provider signature says the event came through the provider. It does not say every action inside the event should be accepted. A signed GitHub event may come from a repository outside the intended allowlist. A signed Slack event may come from a workspace, channel, or app installation that should not control the production workflow. A signed Discord interaction may be triggered in a guild where the invoking member lacks the role that the bot command requires.

Webhook handlers need an authorization layer after signature verification. Check tenant identifiers, installation IDs, repository IDs, guild IDs, channel IDs, account IDs, event types, and actor permissions against a local policy. Reject unknown tenants by default. Avoid global commands that use only a user-supplied name or email address to identify a resource. In high-impact flows, require a second internal approval, especially for role grants, key rotation, publishing, payments, data export, and deployment.

Testing And Incident Response

Signature checks need negative tests. Send a request with no signature, an altered body, an old timestamp, a wrong secret, a wrong public key, a valid signature for the wrong endpoint, and a valid event from the wrong tenant. Confirm that every case fails before queueing or side effects. Discord says it performs automated checks that include invalid signatures for interaction endpoints; teams should run their own checks for every provider.

Logging should capture enough to investigate without storing the secret or sensitive payload content. Record provider, endpoint, tenant, event ID, timestamp, verification result, reason for rejection, and request correlation ID. After suspicious activity, rotate secrets, compare accepted events with provider delivery logs, identify downstream actions, and make sure retries do not recreate the bad state. The goal is not only to block forged requests, but to prove which requests were accepted and why.

Checklist

  • Verify provider signatures before parsing or processing webhook payloads.
  • Preserve the raw request body for verification and test that middleware does not rewrite it.
  • Reject missing signatures, invalid signatures, stale timestamps, unknown tenants, and unsupported event types.
  • Store processed event IDs and make side effects idempotent so retries do not duplicate actions.
  • Use separate secrets for each provider, environment, and high-risk endpoint.
  • Log verification failures and rotate webhook secrets with the same discipline used for bot tokens.

Sources

Related Articles

Continue Reading

Technical editorial diagram showing ordinary admin access, sealed break-glass credentials, phishing-resistant keys, monitoring alerts, and a recovery runbook
Guide

Break-Glass Admin Accounts Need A Runbook

Community platforms and SaaS workspaces need emergency admin access that works during lockouts, outages, founder turnover, and MFA failures. The hard part is keeping that access usable without making it an attacker shortcut.

Technical editorial diagram showing a chat app, local debug logs, crash reporting, support export, redaction controls, and retention policy
Guide

Mobile App Logs Can Become Private Chat Data

Debug logs, crash reports, and support bundles can capture identifiers, tokens, message snippets, and device context. Chat apps need logging policy before troubleshooting becomes data exposure.

Technical OAuth protocol diagram showing an authorization server, redirect URI allowlist, callback endpoint, state and PKCE checks, token exchange, and a blocked open redirect
Guide

Redirect URIs Are OAuth Trust Boundaries

Community apps for Slack, Discord, GitHub, and identity providers often fail at the callback layer. Exact redirect matching, state, PKCE, and callback ownership decide where tokens land.