How to test duplicate and out-of-order webhooks locally
A webhook retry should not automatically repeat the work. The useful test is concrete: after the same event arrives again, which state and side effects should exist?
10 Sep 2026 · original demonstration · prepared by Oddproof with AI and software checksOur downloadable example uses a fictional subscription handler. A paid event activates access; a cancellation closes it. Each event has a stable ID and a source-provided version. These are the fixture’s rules, not a claim that every provider supplies those fields or guarantees their order.
Six cases to put in a local replay matrix
- The paid event arrives twice.
- The cancellation arrives twice.
- An older paid event arrives after a newer cancellation.
- An attempt fails before any work, then retries.
- An attempt fails after state and effects are staged, then retries.
- Work commits but the acknowledgement is lost, causing redelivery.
Write down the final state and expected number of effects before running each test. Otherwise a successful HTTP response can hide an incorrect business result.
The result in this fixture
The intentionally buggy baseline passed one of six scenarios. The corrected example passed all six. A duplicate paid delivery produced four local effect records in the baseline and two in the corrected example. An older paid event incorrectly reopened cancelled access in the baseline.
The corrected example records processed IDs, compares versions and stages state, inbox and outbox records together. It can recognize an already committed event when a lost acknowledgement causes a retry.
Run it yourself
Unzip the sample, enter its directory and run these commands with Node.js. The baseline command intentionally exits with a failure result.
node test/run.mjs --handler=baseline
node test/run.mjs --handler=fixedThe fixture is serial, synchronous and in memory. Its side effects are local records; it sends nothing. It does not test concurrent deliveries, crash durability, signatures, real provider payloads, an external outbox worker or live payments. Six passing cases do not establish exactly-once delivery to another service.
Apply the method to your own flow
Start with one sanitized local reproduction and your actual rules for event identity, stale data and permitted side effects. If the provider does not supply a trustworthy version, design the stale-event rule around the information it does supply. Add concurrency and durability tests separately where those are part of the system.
Download the runnable handlers, fixtures and results. This is original demonstration work, not a customer deployment or payment-system certification.
Rather have it done? Webhook reliability checks →