Source: https://daphnislabs.com/blog/webhook-retries-without-duplicate-orders
Last updated: 2026-09-01T09:00:00+05:30

[All articles](https://daphnislabs.com/blog)

# Handle webhook retries without creating duplicate orders

1 September 2026[Software Engineering](https://daphnislabs.com/blog/category/software-engineering)

![Colourful patch cables connected to a labelled equipment panel.](https://daphnislabs.com/images/blog/photos/webhook-retries-without-duplicate-orders.webp)

A customer pays once, but your integration receives the payment event twice. If every delivery creates an order, a routine retry becomes a fulfilment problem. Treat a webhook as a message that may need to be processed again, and design the receiver so repetition does not repeat the business action.

## Persist receipt before acknowledging it

Verify the request using the provider's documented signature procedure. Store the event identifier and the minimum payload needed for processing in a durable record, then acknowledge successful receipt. Put slower work on a queue. If storage fails, do not return a success response that tells the provider it can stop retrying.

Stripe documents signature verification, retries, duplicate deliveries and ordering considerations in its [webhook guide](https://docs.stripe.com/webhooks) . Follow the guidance for the particular provider you use; a delivery contract is an API requirement, not something to infer from a few successful requests in development.

![Three deliveries of the same event converge on one durable event record and one order, with a separate retry queue.](https://daphnislabs.com/images/blog/editorial/webhook-retries-without-duplicate-orders.svg)

## Protect the business operation too

A unique constraint on the provider event ID can stop the same event from being inserted twice. It does not automatically stop two different events from triggering the same order transition. Give the order its own transition rules. For example, moving an order from awaiting payment to paid should not recreate fulfilment when the order is already paid.

Consider the crash between updating an order and marking an event complete. Keep related database changes in a transaction where possible. For work that crosses system boundaries, record an explicit pending action and use a stable idempotency key when the destination supports one. A worker restart should be able to tell what happened without guessing from log text.

## Build a replay procedure

Test duplicate, delayed and out-of-order events, then terminate a worker during processing. Inspect both order state and the retry record. Give operators a way to retry a failed event without editing a production payload by hand. Keep the original event identifier attached to the attempt history so operators can [follow the request across services](https://daphnislabs.com/blog/observability-follow-one-customer-request) .

These cases belong in the scope of a [custom commerce integration](https://daphnislabs.com/services/shopify-custom-apps) , alongside the successful checkout flow. If several systems share the same event, a [data engineering review](https://daphnislabs.com/services/data-engineering) can map which one owns each state change and where reconciliation is required.

## Related articles

[View all blogs](https://daphnislabs.com/blog)

Software Engineering

### [A database backup is only useful after a restore drill](https://daphnislabs.com/blog/database-backups-restore-drill)

Measure recovery by restoring into an isolated environment and checking the application, roles and dependencies that need the data.

Software Engineering

### [Cache API responses without mixing account data](https://daphnislabs.com/blog/api-caching-without-stale-account-data)

Classify responses before caching them, make cache keys reflect their audience and test what happens when permissions change.

Software Engineering

### [Test tenant isolation beyond the main application query](https://daphnislabs.com/blog/postgres-tenant-isolation-tests)

Review database roles, background jobs and exports together when designing row-level security for a shared application database.
