Skip to content
Cloud Security DeskSearch
Menu

Technical guideWorkload security

Separate webhook authenticity from permission to process it

Verify webhook authenticity at intake, then make durable acceptance, duplicate handling and permission to change business state separate decisions.

Published
Sources checked
Next review
Reading time
11 minutes
Coverage
Stripe · GitHub · IETF
Validated signed bytes enter a durable inbox; a later transition depends on both the accepted event and current business state.
Conceptual model. Accepted intake and an authorized business-state transition are different completion boundaries.

A security and recovery guide for inbound Stripe and GitHub webhooks. It separates raw-body signature verification, timestamp checks, provider-specific redelivery, event identity and asynchronous processing without promising exactly-once effects.

At a glance

Key findings

  • A valid webhook signature authenticates specified delivery data but does not authorize every business action described by that data.
  • Fresh signatures can accompany old events, so timestamp tolerance and duplicate retention have different purposes.
  • Durable acknowledgment, event deduplication and successful side effects are separate states.

A valid signature is the first decision

Accept a webhook only after verifying the provider's signature over the required request data, then separate that decision from durable intake and permission to process the business event. Authenticity does not establish that the event is new, ordered, relevant to the intended tenant or allowed to trigger the proposed state change. The receiver needs an explicit contract for each of those questions. [1][2]

Stripe and GitHub both document signature verification, but their delivery behavior differs. A generic handler that copies one provider's retry assumptions, freshness checks or event identifiers to the other can be wrong even if its cryptographic library is correct. Identify the provider, endpoint secret, event format and supported API version before choosing the intake and recovery rules. [1][2][6][7]

The word received is often overloaded. It can mean that a TCP connection reached the server, that the handler parsed a payload, that a durable inbox accepted it or that a worker completed a business action. A successful HTTP acknowledgment communicates the receiver's response to the request; it does not automatically prove the later completion of asynchronous work. Name the state the endpoint promises when returning success. [8]

For a hypothetical order system, the webhook may report a provider event that is relevant to fulfillment. The receiver first verifies authenticity, then maps the event to the correct account and order, records it durably and later checks the current order state before taking an action. Those steps prevent a valid delivery from becoming an unrestricted instruction channel. The example is a design model, not a deployed payment or order-processing implementation.

Keep this guide's scope narrow. It covers inbound event intake, not a general retry engine or a universal transaction coordinator. Outbound API calls made by the worker may need their own idempotency and authorization rules. A durable inbox can organize work without making every downstream effect occur exactly once. That distinction should appear in the design before a team selects its preferred queue product.

Verify the bytes before transforming them

Stripe's signature troubleshooting guidance requires the unmodified UTF-8 body sent by Stripe and the correct endpoint secret. Middleware that parses JSON and serializes it again can change whitespace, property ordering or encoding, causing verification to fail. Arrange the handler so the supported verification function receives the original body representation before general request processing transforms it. A semantically equivalent JSON object is not necessarily the same signed byte sequence. [4]

The endpoint secret must correspond to the delivery path being tested. Stripe distinguishes secrets associated with registered endpoints and local forwarding workflows. An integration can fail verification because it selected the wrong secret even when both secrets are legitimate. Keep secret selection tied to the endpoint configuration, and troubleshoot using controlled identifiers rather than printing the secret or complete sensitive payload into a shared log. [4]

GitHub documents HMAC validation using the webhook secret and payload contents, with its recommended SHA-256 signature header. It also warns against ordinary equality comparison for digests and points to timing-safe comparison mechanisms. Prefer the provider's supported validation approach for the implementation language. A comparison that happens to return true in a local example is not evidence that the surrounding handler protects against malformed or mismatched deliveries. [2]

Be precise about what the signature covers. GitHub's HMAC guidance describes hashing the payload with the secret; it does not make every HTTP header independently authenticated. Do not infer that a delivery identifier, routing header or other metadata field is cryptographically bound merely because the body signature passed. Use the provider's documented contract and validate the relationships the application relies on rather than treating all parsed request fields as equally trustworthy. [2]

Stripe adds a signed timestamp that its libraries use for a recency check. The documentation gives a default tolerance of five minutes and warns that setting the tolerance to zero disables that check rather than making it stricter. Maintain an accurate server clock and use a deliberate supported tolerance. That interval is not an event-retention setting and should not be copied into a deduplication database expiry rule. [1]

Signature success should lead to controlled parsing and event selection, not immediate execution of arbitrary payload instructions. Check the expected event type and required schema, and reject or quarantine unsupported data under a defined operating policy. Avoid making a validly signed field into an unrestricted file path, URL or command argument. The provider's authenticity does not eliminate the application's responsibility to interpret data safely.

Keep delivery age separate from event age

Stripe generates a new signature and timestamp for each delivery attempt. An old event can therefore arrive with a fresh, valid signature. The recency check helps identify a replayed delivery outside the accepted timestamp window; it does not prove the underlying business event was recently created or has never been processed. Keep delivery freshness, event identity and current business state as separate checks. [1]

The documented windows make this distinction concrete. Stripe attempts automatic delivery for up to three days in live mode. Its Dashboard supports manual resend for up to fifteen days after event creation, while the CLI supports resend for up to thirty days after event creation. These are availability windows for retry or resend, not measurements of event latency or a guarantee that a receiver will process every event within those periods. [1]

The chart uses days for the documented retry or resend windows. It does not place the five-minute signature tolerance on the same axis as if the values were interchangeable retention choices. A receiver may need duplicate and recovery information that outlives the signature check because an event can be delivered again with a new timestamp. Choose the retained record according to supported recovery and business semantics, not simply the shortest visible timer.

Manual resend does not cancel Stripe's automatic retry behavior, even when the manual attempt receives a successful status. A receiver can therefore encounter another delivery after an operator believes the event has been recovered. The worker must recognize the appropriate already-accepted or already-processed state. Treat an operator action as another delivery source, not an exception that bypasses duplicate handling. [1][3]

GitHub has a different contract: it does not automatically redeliver failed webhook deliveries. Its guidance describes manual or scheduled recovery using delivery records. The X-GitHub-Delivery value remains the same when a delivery is requested again. That identifier is useful within the documented delivery model, but it should not be presented as a universal signed timestamp or imported into Stripe's event-identity rules. [6][7]

Recovery ownership should therefore be provider-specific. Name who detects a failed delivery, how it is selected for resend and what the receiver does if it already completed the relevant business action. An operational dashboard that says webhook failed is not enough to determine whether an event is absent from the inbox, waiting for processing or already applied but acknowledged ambiguously.

Event retrieval has its own limit. Stripe's undelivered-event guidance says the Events API returns events created within the last thirty days. Do not assume the provider is an unlimited archive from which any missing inbox record can be reconstructed later. Decide what local recovery evidence the application must retain, and distinguish event retrieval availability from delivery retries and the lifetime of the underlying business object. [3][5]

Figure 01

Retry and resend availability exceed signature tolerance

Stripe documents automatic retries for up to three days in live mode. Manual resend is available for 15 days after event creation in the Dashboard and 30 days after event creation through the CLI. These are documented windows, not processing latency, checked 2026-09-02.

Stripe documented windows are three days for live automatic retries, 15 days for Dashboard resend and 30 days for CLI resend.

Source. Stripe, Stripe webhook delivery, Signature validation, raw bodies, retry windows, duplicate events and ordering. [1].

Method. Source-derived documented values, not measured performance. No numeric conversion. The five-minute signature tolerance is not plotted on this axis. Reviewed 2026-09-02.

Accessible table and figure data
Figure 1 accessible table
Delivery mechanismDocumented window in days
Automatic retry in live mode3
Dashboard manual resend15
CLI manual resend30
Figure 1 accessible table
Delivery mechanismDocumented window in days
Automatic retry in live mode3
Dashboard manual resend15
CLI manual resend30

Persist acceptance before acknowledging work

A durable inbox is a proposed pattern for separating prompt acknowledgment from slower processing. After authenticity and basic event validation, persist the event identity and the information needed to resume work, then acknowledge accepted intake. The worker later claims the record, performs the permitted transition and records the outcome. The sequence diagram is conceptual; it does not claim a particular database or queue provides this complete contract automatically.

Stripe recommends asynchronous event handling and prompt successful responses before complex business logic. Its undelivered-event guidance also discusses tracking events as processing or processed to avoid duplicate work during manual recovery. Use those recommendations to define the receiver's states rather than treating a successful response as an informal promise that all downstream work happened synchronously. [1][3]

There is a meaningful crash boundary before durable acceptance. If the handler acknowledges success first and then loses the event before storing it, the provider may consider delivery complete while the application has no recoverable work item. The proposed sequence places persistence before acknowledgment to avoid that particular gap. The storage operation and error handling still need verification in the actual implementation.

Another crash can occur after acceptance but before the provider receives the acknowledgment. A subsequent delivery should identify the already-accepted event and avoid creating unrelated duplicate work. Use an appropriate atomic storage constraint or transaction for the chosen implementation. A separate read-then-insert sequence can race if two deliveries arrive concurrently. This is a concurrency requirement to test, not a claim of a universally correct SQL snippet.

Processing creates a further boundary. A worker can complete an external side effect and fail before recording completion in its inbox. Retrying may repeat the effect unless the downstream operation has its own safe replay contract or the worker can reconcile its result. An inbox alone cannot guarantee exactly-once behavior across unrelated systems. Record which operation owns idempotency and how uncertain completion is resolved.

Retain enough failure detail to choose the next action without exposing unnecessary payload content. An unsupported event, a transient dependency failure and an authorization mismatch should not all receive the same automatic retry. Some need correction, some need a bounded retry and some need a security investigation. The queue or inbox state should identify that decision owner instead of collecting an indefinite pile of indistinguishable failures.

Preserve the payload interpretation needed for later processing. If a stored event is read after the handler or provider API configuration changes, the worker must still know how to interpret that event safely. Record the applicable event format and version context rather than silently applying the newest parser to every retained item. A successful signature check at intake does not guarantee compatibility with a future worker release.

Figure 02

Acknowledge acceptance before processing the business transition

Durable acceptance precedes acknowledgment, while the worker separately authorizes and records the business transition. Conceptual sequence, not a measured result.

Durable acceptance precedes acknowledgment, while the worker separately authorizes and records the business transition.

Source. Stripe, Stripe webhook delivery, Signature validation, raw bodies, retry windows, duplicate events and ordering. [1]; Stripe, Stripe signature verification failures, Unmodified raw body and endpoint secret [4]; Stripe, Processing undelivered Stripe events, Event retrieval and handling duplicate processing [3]; IETF, HTTP semantics, POST and successful response semantics [8].

Method. Original conceptual sequence synthesizing the cited documentation. It represents design relationships, not observed test results. Reviewed 2026-09-02.

Accessible table and figure data
Figure 2 accessible table
FromToMessage
ProviderIntakeEvent delivery
IntakeIntakeVerify bytes, signature and freshness
IntakeDurable inboxPersist validated event or find duplicate
IntakeProviderAcknowledge accepted delivery
WorkerDurable inboxClaim work safely
WorkerWorkerAuthorize and perform transition
WorkerDurable inboxRecord result and recovery state
Figure 2 accessible table
FromToMessage
ProviderIntakeEvent delivery
IntakeIntakeVerify bytes, signature and freshness
IntakeDurable inboxPersist validated event or find duplicate
IntakeProviderAcknowledge accepted delivery
WorkerDurable inboxClaim work safely
WorkerWorkerAuthorize and perform transition
WorkerDurable inboxRecord result and recovery state

Authorize the transition against current state

Stripe's Event object provides event identity and type together with the event data. Use those fields within the documented payload mode and API contract. Where connected-account or tenant context is relevant, map it to the intended local account before performing a transition. A valid event for one configured account should not be allowed to change another account's object simply because the payload contains a matching-looking business identifier. [5]

Delivery order is not guaranteed by Stripe. Its documentation also warns that created values are in seconds and distinct events can share a timestamp, so that field should not be used as an ordering or duplicate key. A worker should determine whether the proposed transition is valid for the current object state and use the supported provider API when it needs authoritative current information. [1]

Event-ID deduplication handles repeated delivery of the same event, but it is not the only semantic question. Stripe notes that separate Event objects can sometimes describe duplicates and recommends considering the object ID with the event type. Apply that guidance to the relevant event semantics. Do not permanently suppress every future update for an object merely because its object ID and event type matched an earlier legitimate update. [1]

The hypothetical order worker might receive a valid event after the order was cancelled or already fulfilled. The correct response depends on the application's business rule and provider state, not only whether the event's signature is fresh. Define permitted transitions explicitly and preserve an unresolved or rejected result when the event conflicts with current state. A webhook is evidence to evaluate, not an instruction that automatically overrides the application's authorization model.

Use the principle of least event scope. Stripe and GitHub guidance recommend selecting the event types the integration actually needs and checking type or action before processing. Receiving every available event expands parsing, storage and operating responsibilities. A narrow endpoint can have a clearer validation and recovery contract, while a general endpoint needs equally explicit routing rules for each supported event class. [1][6]

Keep business authorization after protocol verification in the architecture. The authenticated provider may legitimately report events with data influenced by users or other actors. Signing that event does not turn every embedded value into a trusted destination or resource choice. Resolve local identifiers and allowed actions through the application's own model, especially before a worker sends information or creates an external side effect.

Review the crash and replay cases

The acceptance suite should distinguish an altered body, an incorrect endpoint secret, an unacceptable signature timestamp and a valid duplicate event. It should also include an old event delivered with a newly generated signature, because that is supported behavior rather than an automatic authentication failure. Test the provider-specific contract instead of forcing all cases into a generic stale-webhook category. [1][2][4]

Exercise concurrent duplicates and the persistence-to-acknowledgment boundary. Then test a worker failure before an effect and an ambiguous failure after the effect but before completion marking. Record what is retried, what is recognized as complete and what requires reconciliation. These are proposed tests for an authorized integration environment; no internet-facing endpoint, provider account or production transaction was executed while preparing this guide.

Validate operator recovery as part of normal operation. A manual Stripe resend can coexist with automatic retries, while GitHub requires an explicit recovery mechanism for failed deliveries. The same inbox and authorization checks should apply to both ordinary and operator-triggered attempts. A special recovery route that skips verification or duplicate handling undermines the controls exercised by the normal endpoint. [1][7]

The completed contract should state what acknowledgment means, which identifiers govern duplicate handling, how long recovery evidence is retained and who resolves uncertain business completion. Those statements make a delivery failure actionable without promising impossible exactly-once semantics. They also prevent the strongest-looking part of the system, a valid signature, from being mistaken for proof that every later processing decision is safe.

Method and provenance

Primary documentation and standards were reviewed on September 2, 2026. The guide combines source-supported behavior with explicitly framed implementation recommendations and hypothetical examples.

No customer deployment, production environment or live cloud configuration was tested. Product behavior is limited to the cited documentation and stated scope; actual versions, policies and application behavior require verification.

AI assistance. AI-assisted research synthesis, drafting and consistency checks. No human review or firsthand deployment experience is claimed.

Published under the Cloud Security Desk organizational byline. Read the practitioner guide policy.

References

  1. Stripe webhook delivery Stripe. Accessed .
  2. GitHub webhook validation GitHub. Accessed .
  3. Processing undelivered Stripe events Stripe. Accessed .
  4. Stripe signature verification failures Stripe. Accessed .
  5. Stripe event object Stripe. Accessed .
  6. GitHub webhook operating practices GitHub. Accessed .
  7. Handling failed GitHub deliveries GitHub. Accessed .
  8. HTTP semantics IETF. Accessed .