Payment webhook retries can deliver the same notification more than once, and related events can arrive out of order. An integration needs separate controls for authenticating a message, recording receipt, applying business changes and recovering unfinished work. A successful HTTP response proves only part of that process.
A retry is a delivery event
A payment provider sends a webhook because something happened in its system. The provider then needs a response from your endpoint. If it cannot establish successful delivery, it can retry according to its documented policy.
Your application has another problem: whether it finished the work triggered by the message. Sending a response and completing a refund ledger entry are different operations. A design that treats them as one indivisible step needs to explain what happens when the process stops between them.
For authentication, use the separate guide to verifying payment webhook signatures. Correct processing starts after the integration establishes that the message is genuine.
Stripe: event IDs and missing objects
Stripe’s webhook documentation says delivery order is not guaranteed. It recommends tracking event IDs for duplicate deliveries and warns that snapshot-event creation timestamps have second-level precision, so distinct events can share a timestamp.
A timestamp is therefore a poor substitute for an event identifier. A local rule that discards every message older than the last timestamp can drop a valid event.
Stripe also describes retrieving missing objects through its API. That gives the application a way to investigate an event without depending on every earlier notification having arrived first. The relevant account and API context still need to be correct.
Adyen: use its event identity
Adyen’s handling guidance describes duplicate events with the same eventCode and pspReference, even when eventDate and other fields differ. It directs integrations to use details from the latest event.
Do not copy Stripe’s event-ID rule into every provider adapter. Define the identifier and update semantics from the documentation for the webhook product being consumed.
| Concern | Stripe documentation | Adyen documentation |
|---|---|---|
| Repeated delivery | Track event IDs | Recognise the documented event-code/reference pair |
| Ordering | No delivery-order guarantee | Handle event timing and updated duplicate details |
| Receipt and work | Acknowledge promptly | Verify, store, acknowledge, then process |
| Recovery | Inspect delivery logs and retrieve needed objects | Inspect webhook status and retry behaviour |
The table summarises the linked documentation, not a universal contract for every product either company offers.
Store before acknowledging
Adyen’s documented sequence is to verify the message, store it in a database or queue, return a successful HTTP response, and then apply business logic. That separation is useful when processing requires several downstream steps.
The durable record should tell an operator what happened after receipt. An internal model might distinguish received, processing, completed and exception states. These are suggested application states, not provider status values.
Suppose the receiver acknowledges an event and a worker later fails before updating an order. Provider delivery logs can correctly show success while your order remains wrong. Replaying only provider-marked failures would miss that case.
A recovery view should therefore compare received messages with completed business processing. Keep enough correlation data to connect an event, its payment object and the local order without exposing unnecessary personal or payment details.
Make the business effect safe to repeat
Deduplicating the incoming message is one layer. The operation it triggers needs its own protection against repetition.
Imagine two workers read the same event before either marks it completed. A “seen event” check followed by an independent order update can still race. The implementation needs an appropriate transaction, uniqueness constraint or equivalent coordination for its data store.
This is an engineering pattern, not a vendor API guarantee. Check the actual failure boundaries: a crash before storage, after acknowledgement, during a database update and after a downstream request succeeds but before the local result is recorded.
For requests sent to a payment API, idempotency keys address a related but separate retry problem. An inbound webhook identifier is not automatically an outbound idempotency key.
Replay with evidence
Stripe’s endpoint-management documentation describes automatic retries and recovery of undelivered events. Use the current provider instructions for retry windows and manual tools.
Before replaying a backlog, establish the intended outcome for each event type. A replay should repair missing local work without repeating an already completed financial effect. Keep a count of selected, processed, skipped and failed records, with identifiers that let an operator investigate the remainder.
Test the scenario with the provider’s supported tooling and your application’s own failure controls. The payment API sandbox guide covers why a happy-path test is insufficient.





