Blog Technical content

Webhook security: signatures, replay, and duplicate deliveries

An illustrative receiver contract that separates authentic requests from safe, repeatable processing.

AI-assisted article. Sources and examples follow our editorial policy.

Webhook security: signatures, replay, and duplicate deliveries, Infosec Writing Studio article image

A webhook receiver can accept an authentic request and still perform the wrong action twice. Signature verification addresses whether a request satisfies the provider’s authentication scheme. Duplicate handling addresses whether processing that request again is safe. A useful webhook security guide explains both decisions and the state between them.

This article builds an illustrative receiver contract for a fictional security integration. It creates an investigation when an upstream service reports a new finding. The tables describe intended behavior, not a tested implementation. Provider-specific signing formats, retry policies, and response requirements must be checked before implementing the contract.

Identify exactly what the signature covers

GitHub’s delivery-validation documentation describes calculating an HMAC SHA-256 signature using the webhook secret and request body, then comparing it with the supplied signature. It also recommends a comparison method that avoids timing attacks.

That creates a precise documentation requirement: identify the bytes being authenticated. Re-serializing parsed JSON can change whitespace or encoding and produce different bytes. Middleware behavior belongs in the implementation review because the receiver needs the body representation expected by the provider’s scheme.

Also inventory fields outside the signed material. A signed body does not automatically authenticate every HTTP header. If an integration uses a delivery identifier from a separate header, explain how it uses that value and which guarantees the provider actually supplies. Do not extend the signature’s guarantee to unrelated metadata.

For API teams, this is a concrete application of the trust-boundary questions behind the OWASP API Security Top 10.

Write the receiver decision table

The fictional receiver has two durable records: an accepted delivery and the resulting investigation. Its processing policy is deliberately separate from its response to the HTTP request.

ConditionReceiver decisionEvidence to preserve
Signature is missing or invalidReject before creating workSanitized failure category and receipt time
Valid supported event, first accepted deliveryPersist accepted work, then acknowledge under the provider’s contractDelivery reference, event type, authenticated payload reference
Valid delivery already completedAvoid creating another investigationOriginal processing result and duplicate observation
Valid delivery with work still pendingKeep one pending operationCurrent state and retry ownership
Valid but unsupported eventFollow the documented ignore policyEvent type and reason for ignoring
Accepted work fails downstreamRetry or reconcile the stored operationAttempt count, failure category, next action
Durable acceptance cannot be recordedReturn the provider-appropriate failure responseReceipt failure and operational alert

The table is a design worksheet. An implementation must define how concurrent requests reach these decisions atomically. Checking a database and inserting later can permit two workers to make the same “first delivery” decision.

Avoid logging the full payload simply to make the table easier to fill. Store only the fields needed for processing and investigation, with access and retention controls appropriate to their contents.

Separate duplicate handling from replay protection

A repeated authentic request may be a legitimate redelivery or an unwanted replay. A signature alone does not establish that a request is recent. Some provider schemes authenticate timestamps and prescribe a freshness window; others require a different design. State the actual provider’s mechanism instead of promising generic replay protection.

GitHub’s webhook best practices describe using X-GitHub-Delivery to identify deliveries, including the fact that a redelivery uses the same value. That helps an integration recognize ordinary duplicate deliveries. Because GitHub’s described body-signature calculation does not include that header, the header alone should not be presented as a tamper-proof replay defense.

For the fictional receiver, the design review would therefore ask two separate questions. Can it avoid repeating a business action when the provider redelivers an event? What prevents a captured authentic request from producing new work if unsigned metadata changes? Those questions may need different controls.

Payload hashes also need careful treatment. Two legitimate events can have identical bodies. A design that suppresses every repeated body may lose legitimate work. Document which authenticated event identity or business-operation key is available, and identify any residual limitation explicitly.

Make acknowledgement meaningful

An HTTP success response tells the sender something about receipt; it does not necessarily prove that all downstream work finished. The integration guide should define that boundary in one sentence.

For this fictional service: “A successful acknowledgement means the receiver durably recorded the accepted event for processing. Investigation creation may complete later.”

GitHub recommends responding within ten seconds and suggests asynchronous processing. That timeout is provider-specific. It should not become a universal number copied into every webhook guide. The durable acceptance point matters more than a diagram that simply says “send to queue.”

If acknowledgement happens before storage succeeds, work can disappear after the sender stops retrying. If acknowledgement waits for several slow downstream services, the sender may retry work that is already underway. Document which component owns recovery in both situations.

Describe failures readers can act on

A troubleshooting section should distinguish authentication rejection, unsupported event types, acceptance failures, and processing failures. Those states lead to different actions. Replaying a request will not fix a mismatched secret; rotating a secret will not repair an unavailable downstream database.

Use a sanitized correlation reference so an operator can trace receipt through processing without exposing credentials or an entire finding payload. Explain where the operator sees pending work, when a failed operation becomes eligible for retry, and how to reconcile an uncertain downstream result.

Secret rotation also needs an explicit transition policy. Review how a receiver and sender move to a new secret, whether an overlap period is supported, and who removes the previous value. Do not claim overlapping verification is available unless the integration supports it.

Review the contract before publishing the tutorial

A writer can produce the table before code exists, but the published tutorial should distinguish proposed behavior from verified behavior. Ask an engineer to review raw-body handling, constant-time signature comparison, concurrent duplicate acceptance, storage failure, and recovery after a lost downstream response.

An external-writer brief can assign an owner to each unresolved behavior. The prepublication technical review should then check that the prose matches the actual integration version.

The most useful final example is a delivery walkthrough with observable states: accepted, pending, completed, and retried. Each state should identify who acts next. Readers can then diagnose a failed integration without treating every problem as a signature error.

Continue your research

Use the Reference Indexes for Definitions and Evergreen Guidance.

Editorial support

Need a Security Article Researched, Written, or Reviewed?