A webhook can come in late, twice or out of order. Here is what Stripe and Adyen tell a team to do in each case.

Payment webhooks are HTTP messages a payment provider sends to a platform’s server when something changes: a payment is authorized, a bank confirms a transfer, a customer opens a dispute. Many payment outcomes arrive only this way, after the API call has returned. Payment webhooks are not guaranteed to arrive once or in order, so the receiving code has to verify each message, answer fast, drop duplicates and process events in a queue.

Why payments depend on webhooks

Stripe describes webhook events as the way to respond to asynchronous events, such as a customer’s bank confirming a payment, a dispute or a recurring payment that succeeds. Adyen makes the same point from the order side: the webhook carries the final outcome as soon as it is available, so the platform can mark an order paid and start shipping.

The API response tells a platform what it asked for. The webhook tells it what happened. A platform that books revenue, releases goods or pays out sellers from the API response alone will be wrong whenever the outcome changes later.

Acknowledge first, process later

Both providers ask for the same order of work: receive, store, acknowledge, then run business logic.

Stripe Adyen
Success response Any 2xx status A successful status such as 200 or 202
When to answer Before any complex logic that might cause a timeout Before validating or processing the data
Processing advice Use an asynchronous queue to absorb spikes Store the message in a database or queue and process it later

Stripe’s example is concrete: return a 200 before updating a customer’s invoice as paid in the accounting system. Adyen warns that errors in business logic can otherwise cause failing webhooks. It also sets a clock: if it gets no success response within 10 seconds, for example because the server is down, it marks the webhook as Failing and puts it in a retry queue.

Verify every message

An unverified endpoint lets anyone post a fake “payment succeeded” event. Stripe’s documentation says an attacker could then trigger fulfillment, grant account access or change records.

Stripe signs each delivery with an HMAC using SHA-256. The Stripe-Signature header carries a timestamp (t=) and one or more signatures; only the v1 scheme is valid for live events, and Stripe tells integrators to ignore every other scheme to prevent downgrade attacks. Because the timestamp is part of the signed payload, it also guards against replay. Stripe’s libraries reject events older than 5 minutes by default, and Stripe warns against a tolerance of 0, which turns the check off. Each retry gets a new timestamp and signature.

Adyen lists domain and IP allowlisting and basic authentication as one layer of security, and HMAC signature verification as another strong measure. It says to verify the HMAC signature before using the payload. If the message is not secure, Adyen does not recommend accepting it.

Duplicates and ordering

Duplicates are normal. Stripe says an endpoint may receive the same event more than once and suggests logging processed event IDs. In some cases Stripe sends two separate Event objects for one change, and the object ID in data.object plus the event type identifies them. Adyen duplicates share eventCode and pspReference, while eventDate and other fields can differ; Adyen says to use the details from the latest one.

Order is not guaranteed either. Stripe does not promise to deliver events in the order they were generated, and its created timestamp is in seconds, so distinct events can share one. Stripe says not to use created to order events or detect duplicates, and to fetch missing objects through the API instead. Adyen tells integrators to check the event timestamp and, where present, a sequenceNumber.

The same discipline applies to outgoing calls. A platform that retries its own API requests needs idempotency keys; see payment API idempotency.

Retry schedules

Stripe Adyen
Retry window Up to three days in live mode, with exponential back-off; three times over a few hours in a sandbox A few immediate retries, then a retry queue for up to 30 days
Intervals Shown per event in the Event deliveries tab 30 seconds rising step by step to 8 hours
Manual tools Resend an event; a manual resend does not cancel automatic retries Retry or Ignore a queued webhook

Retries end in a different place for each provider. Stripe stops retrying an event if the endpoint has been disabled or deleted. Adyen keeps the webhook in the queue until a retry succeeds, someone selects Ignore, or the 30 days run out. Adyen can send alerts after 5 retry attempts and again after 7 days of failure. It also notes that a fixed endpoint can face a burst of queued webhooks and answer 429 if it rate-limits Adyen.

A webhook checklist for payment teams

  • Verify the signature on the raw request body before reading any field.
  • Store the event, answer with a success status, then process it from a queue.
  • Keep a table of processed event IDs and make every handler safe to run twice.
  • Do not rely on arrival order; fetch the current object from the API when state matters.
  • Watch for failing deliveries and alert before retries run out.
  • Reconcile payouts and balances against provider reports, not against webhooks alone.

Webhooks also carry dispute and payout events in marketplaces; see marketplace chargebacks and payment gateway api.