Webhooks

Delivery and retries

Learn what counts as a successful delivery, how the six-attempt retry schedule runs, how to handle unordered and duplicate events, and how to recover them.

tmbilling records each webhook event with the change that caused it, then delivers it and retries a failed delivery on a six-attempt schedule. Build your endpoint for events that arrive out of order or more than once.

Rely on the outbox

tmbilling does not deliver an event from the request that caused it. It records the event and its deliveries in the same database transaction as the state change, then delivers from that queue afterwards.

That is why a webhook cannot disagree with reality. No window exists where a subscription was cancelled but the event vanished with a dying process, or where you received an event for a transaction that then rolled back.

Understand the retry schedule

tmbilling counts any 2xx as success. Everything else — a 4xx, a 5xx, a timeout, a refused connection — is a failure, and it retries.

AttemptSent after the previous failure
1immediately
21 minute
35 minutes
430 minutes
52 hours
66 hours

Six attempts span a little under nine hours. After the sixth, tmbilling marks the delivery failed and stops retrying automatically.

Some systems treat a client error as permanent. tmbilling does not. A 400 from a webhook endpoint almost always means you just deployed a bug, not that the event is permanently bad — so retrying gives you a window to fix it.

Handle unordered delivery

tmbilling does not order deliveries. invoice_paid can reach you before invoice_posted, especially after a retry, because a retried event arrives late by definition.

Two things make that manageable. Every event carries occurred_at, and content carries a full snapshot of the resource rather than a delta. So compare occurred_at against what you have already recorded and ignore the older event, instead of reconstructing a sequence.

Deduplicate on the event id

tmbilling delivers at least once. When your handler succeeds but its 2xx never arrives, you get the same Tmbilling-Event-Id again.

Record the ids you have processed and skip repeats. It takes a few lines and removes a whole class of duplicate-side-effect bugs: the welcome email sent twice, the account provisioned twice.

Find out what happened

GET/v1/webhook_endpoints/{id}/deliveries

Lists every attempt with the response your server gave. Look here first when something "did not arrive" — usually it did, and your server refused it.

GET/v1/events/{evt}

Gives you one event with its per-endpoint delivery record.

Recover missed events

CallUse it when
POST /webhook_endpoints/{id}/deliveries/{del}/redeliverYou fixed your handler and want one event back. It runs synchronously and returns the attempt, so you learn immediately whether the fix worked.
POST /webhook_endpoints/{id}/redeliver_latestYou want a quick end-to-end check after a deploy.
GET /businesses/{biz}/eventsYour endpoint was down longer than nine hours. Replaying deliveries one at a time is the wrong tool — list the events for the window you missed and reconcile from those.

Meet the endpoint requirements

RequirementWhy
Use HTTPS in live modeThe payload describes your customers and their money.
Store, answer 2xx, then workA slow handler gets retried while it is still working.
Answer 2xx for events you ignoreAn event type you don't handle is not an error. A 4xx buys you six retries of something you were always going to discard.
Copyright © 2026