The scenario
A made-up sender signs each delivery with a shared secret and gives every delivery an identifier. Our made-up receiver follows one rule order: verify the signature first, then check whether the identifier has been handled, then record it, then act, then answer. Six requests arrive. This is a simulation written for this page: it uses an in-memory set and a single process, and it was run on 11 October 2026 to produce the table below.
The ledger
Reading down the table: the first delivery acts once. A retry after a timeout and a redelivery the next day carry the same identifier, so they return success without acting. A new event with identical content but a new identifier is a different event and acts. A copy of an event with its title altered fails the signature and does nothing. An unsigned request does nothing.
If the matrix is wider than the box, scroll horizontally to read every column. Keyboard: focus the matrix and use Left/Right.
# delivery signature id handled before response actions so far
1 first attempt for dlv_demo_101 ok no 200 1
2 sender retries 101 after a timeout ok yes 200 1
3 redelivery of 101 the next day ok yes 200 1
4 new event dlv_demo_102, same title ok no 200 2
5 101 with the title altered bad - 401 2
6 dlv_demo_103 with no signature bad - 401 2
Final: 2 actions, handled ids dlv_demo_101 and dlv_demo_102What the simulation leaves out
A real receiver has problems this one does not. Two copies of the same delivery can arrive at the same moment, so recording the identifier has to be one atomic step, not a check followed by a separate write. A crash between recording the identifier and performing the action loses the action unless the two are recorded together in one durable step, or the action is queued from the same record. A store that forgets identifiers after a time lets a late redelivery act again. An in-memory set is lost when the process restarts. The identifier here is treated as part of what the signature covers; if a real sender puts it only in an unsigned header, a captured request can be replayed with the header changed, and the signature guide explains how to key on the signed bytes instead.
Do not assume an ordering guarantee between events unless the sender's documentation states one. The example shows the rule order, not a production design.
- Record the identifier in one atomic step.
- Queue the work from the same durable record.
- Decide how long handled identifiers are kept, and what happens to an older repeat.
Using it as an acceptance check
Rows 1 to 6 translate directly into tests: act once; repeat on timeout returns success without acting; a redelivery acts once; a different identifier acts; an altered body and a missing signature do not act. Add two more for a real receiver: the same identifier delivered twice at the same instant, and the process killed between recording and acting. The paid outcome for one signed event type is accepted by tests of exactly this kind against synthetic deliveries. It does not claim that no live event is ever lost.
Sources and limits
- Shopify: HTTPS webhook subscriptions Checked 2026-10-11.
- Shopify retries failed deliveries 8 times over 4 hours and recommends de-duplicating by the X-Shopify-Webhook-Id header or processing idempotently.
- GitHub: best practices for using webhooks Checked 2026-10-11.
- GitHub expects a 2XX response within 10 seconds and suggests queuing payloads so a server can acknowledge before processing.
- GitHub: validating webhook deliveries Checked 2026-10-11.
- The signature should be verified before any further processing.