WHCC webhook delivery is at-least-once, not exactly-once. Your endpoint has to handle two things correctly: responding fast enough to avoid self-inflicted retries, and treating duplicate deliveries as normal rather than exceptional.
ConfirmationId + SequenceNumber + event typeEvery notification carries a ConfirmationId and a SequenceNumber — top-level JSON fields on V2, top-level form fields (also inside the json payload) on V1. WHCC may send the same notification more than once, so your handler has to be idempotent. But the key matters, and so does what you do when it repeats.
Use all three parts of the key. A ConfirmationId is not unique to one order. When WHCC splits an order across shipments or production paths, the resulting orders share a ConfirmationId and are told apart by SequenceNumber (for example "1" and "1.2") — each with its own OrderNumber and its own tracking. Keying on ConfirmationId + event type alone will silently discard the second half of every split order.
A repeat is not always a no-op. Even with the full key, a later delivery can carry more data than the earlier one — most commonly a Shipped notification re-sent moments later with additional tracking numbers as more packages are scanned. Treat a repeat as a reconcile, not a discard: re-apply the payload so the newer content wins, and make the side effects (emails, downstream syncs) fire only on genuinely new information.
Duplicates are structural, not exceptional:
POST your endpoint successfully, then fail to record that the delivery succeeded, and send it again.None of these are bugs. Key on (ConfirmationId, SequenceNumber, event type), reconcile rather than drop, and repeats become harmless.
Your endpoint has 100 seconds to return a response once WHCC opens the connection. If your endpoint is still processing when the timeout elapses, WHCC treats the request as failed and retries it — even if your endpoint eventually would have returned 200. A slow endpoint therefore generates its own retries and its own duplicates.
Return a 2xx as soon as you have durably recorded the notification. Do any slow downstream processing (image lookups, order sync, notifications to other systems) asynchronously, after you respond.
What happens next depends on the response WHCC receives:
| Response | Outcome |
|---|---|
Any 2xx |
Success. Delivery complete; never retried. |
301, 302, 303 |
The redirect is followed, but replayed as a GET with no body, so your endpoint never receives the notification. If that GET returns a 2xx, WHCC records the delivery as successful and never retries it — the notification is lost silently. Never register a callback URI that redirects — including a plain http:// URL that your server upgrades to https://. Register the final destination directly. |
Any 4xx |
Terminal. WHCC stops sending this notification permanently. |
Any 5xx |
Retried. |
| Connection failure (refused, reset, DNS failure) | Retried. |
| No response within 100 seconds | Retried. |
Upcoming changes. Two parts of the table above will change in a future release. No date is set yet, so write your integration to be correct under both the current and the future behaviour.
408 Request Timeout and 429 Too Many Requests will become retryable, rather than terminal like the rest of the 4xx range. Until then, treat them as terminal — do not rely on WHCC retrying them.3xx will be treated as a failed delivery. If your endpoint currently returns a redirect, point your registered callback URI directly at the final destination now.Retries use exponential backoff: the delay before attempt n is 6^(n-1) minutes. These are minimums — a retry becomes eligible after the delay elapses and is sent on the next delivery pass, so the real gap can be slightly longer, particularly at busy times. WHCC currently makes up to 7 attempts total (the initial attempt plus 6 retries) before giving up.
| Attempt | Minimum delay since previous attempt | Minimum elapsed since first attempt |
|---|---|---|
| 1 | — (initial delivery) | 0 |
| 2 | 6 minutes | 6 minutes |
| 3 | 36 minutes | 42 minutes |
| 4 | 216 minutes (3.6 hours) | ~4.3 hours |
| 5 | 1,296 minutes (21.6 hours) | ~1.1 days |
| 6 | 7,776 minutes (5.4 days) | ~6.5 days |
| 7 | 46,656 minutes (32.4 days) | ~38.9 days |
A single notification can therefore keep retrying for up to ~39 days after the triggering event, if your endpoint keeps returning a retryable failure. Size your idempotency storage with that window in mind — a repeat can arrive more than a month after the original. If you expire dedup keys after a week, a delivery on attempt 7 will look brand new.
Upcoming change. That last row is longer than intended. In a future release the cap becomes 6 attempts, ending the window at ~6.5 days. Retaining dedup keys for ~39 days is correct under both the current and the future schedule, so size for 39 days now and you will not need to revisit it.
After the final attempt fails, WHCC stops retrying that notification. There is currently no partner-facing endpoint to query undelivered notifications, so a monitored, reliably-responding endpoint is the only way to guarantee delivery.
Security and Signatures describes how to validate that a webhook request actually came from WHCC.