Delivery and retries
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.
| Attempt | Sent after the previous failure |
|---|---|
| 1 | immediately |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 6 hours |
Six attempts span a little under nine hours. After the sixth, tmbilling marks the delivery failed and stops retrying automatically.
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
/v1/webhook_endpoints/{id}/deliveriesLists every attempt with the response your server gave. Look here first when something "did not arrive" — usually it did, and your server refused it.
/v1/events/{evt}Gives you one event with its per-endpoint delivery record.
Recover missed events
| Call | Use it when |
|---|---|
POST /webhook_endpoints/{id}/deliveries/{del}/redeliver | You 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_latest | You want a quick end-to-end check after a deploy. |
GET /businesses/{biz}/events | Your 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
| Requirement | Why |
|---|---|
| Use HTTPS in live mode | The payload describes your customers and their money. |
Store, answer 2xx, then work | A slow handler gets retried while it is still working. |
Answer 2xx for events you ignore | An event type you don't handle is not an error. A 4xx buys you six retries of something you were always going to discard. |