A source-checked field guide. The linked primary sources were reviewed for the explanations in this note. Provider examples are not universal terms. This is a local editorial preview, not individualized advice; publication review remains pending.

A webhook is an asynchronous claim that something changed. It is not a one-time command arriving in a guaranteed sequence. Build the receiver so the same valid message can arrive again, a later state can arrive first, and slow business logic cannot force the provider to keep retrying.

Stripe says delivery order is not guaranteed, recommends tracking event IDs for duplicate deliveries, and requires the raw request body for signature verification. Adyen likewise documents signature verification, durable storage before business processing, duplicate cases, timestamps, and sequence numbers for event types that provide them (Stripe; Adyen). These are provider-specific contracts; field names and signing algorithms are not interchangeable.

Keep the intake path short

The request handler should read the exact bytes required by the provider’s signature scheme, obtain the relevant signature header or field, and verify before trusting any payload value. Check the provider’s timestamp or replay tolerance where its scheme defines one. Use a constant-time comparison through the provider’s supported library when available. Stripe warns that parsing or otherwise changing the raw body before verification breaks its signature check. Adyen’s HMAC guide shows that some webhook types sign selected fields while other types sign the raw body, which is why one verifier cannot be assumed for every Adyen event (Adyen HMAC guide).

After verification, write an immutable inbox record inside a database transaction: provider, account, endpoint, event ID, event type, object reference, provider-created time, received time, signature-key version, raw-payload location, and processing state. Enforce a unique key appropriate to that provider’s event identity. Then acknowledge quickly with the success status the provider expects. A queue consumer, not the public request handler, should perform fulfillment, ledger, email, or entitlement work.

Deduplicate effects, not just deliveries

The first line of defense is the provider event ID. If the same ID returns, record the delivery attempt and avoid repeating business effects. Stripe also notes that separate event objects can represent the same underlying object and event type; therefore the operation itself needs an idempotent guard too. Examples include a unique order_id + fulfillment_stage, invoice_id + paid, or refund_id + ledger_posting constraint.

Do not discard a duplicate before signature verification. An attacker should not be able to probe stored event IDs to obtain a success response for an invalid request. Verify, store or recognize, acknowledge, then no-op safely.

Order by state, not arrival

An event’s arrival time is not the payment state machine. If invoice.paid arrives before invoice.created, retrieve the current resource where the provider recommends that pattern or upsert the known facts without regressing state. A late “authorized” event must not move a locally captured payment backward. Store provider event time and receive time separately; timestamps alone may tie or lack sufficient ordering precision.

Define permitted transitions for the business: pending to authorized, authorized to captured or canceled, captured to partially or fully refunded, and explicit exception states. Provider models vary, so translate provider events into a local vocabulary while preserving the originals. The authorization lifecycle guide supplies the financial distinctions, and API retry safety covers the outbound half of the same distributed workflow.

Operate the inbox

Use retry counts and a dead-letter or manual-review state for failures. Make handlers transactional: either the business effect and processed marker commit together, or neither does. When an external side effect cannot share the transaction, write an outbox record and deliver it idempotently.

Alert on signature failures, sustained delivery lag, queue depth, poison events, unexpected event types, and impossible state transitions. Rotate signing secrets with an overlap window only as the provider supports, and record which key verified each message. Restrict raw-payload access because events can contain customer and transaction data.

Test a valid event, altered body, stale replay, duplicate ID, two distinct events for one object, reverse order, missing predecessor, handler crash after the database write, and secret rotation. These are recommended scenarios; this article does not claim an integration was executed or certified.

Sources

Evidence & dates

Prepared 19 Sept 2026 · source checks 19 Sept 2026 · website publication pending. Undated means no publication date was established on the reviewed page.

Stripe · Receive Stripe events in your webhook endpoint

Stripe-specific raw-body signature verification, retry behavior, event ordering limits, duplicate handling, and fast acknowledgement.

Source publication date: undated · checked 2026-09-19 · full page reviewed · evidence: self-reported · recheck by 2026-12-19

Read the primary source ↗
Adyen · Handle webhook events

Adyen-specific verification, storage-before-processing, acknowledgement, timestamps, sequence numbers, and duplicate identifiers.

Source publication date: undated · checked 2026-09-19 · full page reviewed · evidence: self-reported · recheck by 2026-12-19

Read the primary source ↗
Adyen · Verify HMAC signatures

Provider-specific HMAC payload construction, raw-body cases, constant provider fields, and secret rotation behavior.

Source publication date: undated · checked 2026-09-19 · full page reviewed · evidence: self-reported · recheck by 2026-12-19

Read the primary source ↗
KEEP THE THREAD GOINGRetry a payment API call without creating a second operation →Authorization, capture, void, and refund are different state changes →ACH credit or debit? Follow who starts it. →Reading path: Build for the payment that goes wrong ↗Return to the library →