Delivery, Retry & Dedup
What your endpoint must do
- Verify the signature over the raw request body and check timestamp freshness before trusting or deduplicating the request.
- Durably accept a new event in an inbox or queue keyed by
event_id. Commit that write before returning 2xx, ideally within ~10 seconds. If storage fails, return503so delivery can be retried. - For a valid event already durably accepted, return 2xx again. Do not repeat its business effect, and do not return 4xx merely because its ID is familiar.
- Process the inbox asynchronously. Commit each business effect and its dedup key together, or use an equivalent idempotent operation. Retry local processing until it succeeds; TradeOn stops sending after your 2xx.
- Tolerate unknown fields and valid, signed unknown event types. Record or ignore unknown types and return 2xx (versioning).
A process-local set of IDs is not a durable inbox. It loses protection on restart and can lose an event if you mark it processed before its work is safely stored.
Retry policy
The delivery log exposes an attempt counter, delivery_attempts. The outcome of an
attempt determines the next action:
| Outcome | What triggers it | What happens next |
|---|---|---|
| Success | HTTP 2xx | Marked delivered. No further automatic attempts. |
| Transient | HTTP 408, 429, any 5xx; network or DNS failure; 30-second attempt timeout | Retried while automatic attempts remain. |
| Terminal | Other HTTP 4xx; a blocked destination; missing URL or secret; a permanent payload error | Marked failed. No automatic retry. |
The automatic backoff is 1 minute → 10 minutes → 1 hour. There are at most three retries after the first attempt. Delays start from the latest failed attempt, not from event creation; these are retry delays, not a delivery-time guarantee.
attempt #1 fails transiently → attempt #2 after 1 minute
attempt #2 fails transiently → attempt #3 after 10 minutes
attempt #3 fails transiently → attempt #4 after 1 hour
attempt #4 fails transiently → failed; no more automatic attemptsA 4xx other than 408/429 stops the chain immediately. Correct your endpoint or URL, then use the manual replay procedure for failed, stored events.
Per-attempt timeout
Each attempt has a 30-second wall-clock limit, including DNS, TLS, the request, and reading the response body. Aim to acknowledge durable intake within ~10 seconds so there is room for network delays. Put lengthy processing behind your inbox.
Dedup contract
Keep two separate deduplication records:
| Layer | Key | Purpose |
|---|---|---|
| Delivery intake | event_id (also X-Event-Id) | Accept one stored event once, despite repeated delivery or manual replay. |
| Purchase business effect | (data.purchase_id, event) for every purchase.* type | Apply that effect once to that purchase attempt, including late rollbacks. |
| Deposit business effect | data.provider_uuid for balance.deposited | Credit a deposit once even if two stored events have different event IDs. |
Scope these keys by merchant if your receiver serves multiple merchant accounts.
Do not deduplicate purchases on custom_id alone: buy-by-name can produce multiple
attempts, and a completed purchase can later receive a distinct rollback event.
For example, events A and B can both report deposit UUID P. Store both delivery
IDs as accepted, but credit P only once. A retry of A receives 2xx without another
inbox entry or credit. Record the financial effect and business key in the same
transaction so a crash cannot apply one without the other.
Retries and manual replay preserve the same event ID and raw body. The delivery
X-Timestamp and X-Signature are regenerated on each attempt. Verify them even when
the event ID is already present.
Operational tuning
Monitor your inbox processing failures as well as the TradeOn delivery log. A delivered event means your endpoint returned 2xx; it does not mean your background worker completed its work. The sender may deliver an event more than once, and automatic retries are finite.
Webhook delivery retries apply only after an event has been stored. Event creation and business state changes do not provide a guarantee of complete catch-up after an outage. Periodically reconcile purchase history and balance, and investigate gaps using delivery IDs and your own inbox. There is no guaranteed webhook delivery latency or replay retention window documented here.