Webhooks

Register an endpoint and receive every billing state change as a signed, retried webhook, so your systems learn about renewals, failures and disputes at once.

Most of billing happens without an API call from you: a trial ends, a renewal invoice goes out, a card declines on the third retry, a buyer opens a dispute. Webhooks tell your systems about it.

An endpoint receiving invoice_paid costs you nothing per minute. A poller asking "is it paid yet?" every second spends your whole rate-limit budget to learn nothing most of the time.

Register an endpoint

POST/v1/webhook_endpoints
curl -X POST $TMBILLING_URL/v1/webhook_endpoints \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.example.com/hooks/tmbilling",
    "event_types": ["invoice_paid", "invoice_payment_failed", "subscription_cancelled"]
  }'

Store the signing secret immediately — tmbilling returns it once, when you create the endpoint.

Use event_types to subscribe to a subset, or omit it to receive everything. You can also scope an endpoint to one business, so a multi-business organization routes each business's events to a different service.

Read the payload

tmbilling sends a POST with this body and nothing else:

{
  "object": "event",
  "id": "evt_8f14e45fea6c4a1f",
  "event_type": "invoice_paid",
  "occurred_at": "2026-09-16T10:31:07.412Z",
  "api_version": "2026-06-20",
  "source": "billing_clock",
  "user": null,
  "content": { }
}
FieldTypeWhat it holds
idstringThe event id. tmbilling also sends it as the Tmbilling-Event-Id header — deduplicate on it.
event_typestringWhat happened. See the event reference for the full catalogue.
occurred_atISO-8601When the change happened, which is not always when you received it.
sourcestringWhat caused it: the billing clock, an API call, the dashboard, or a provider webhook.
userstring or nullWho did it, when a person did.
contentobjectA full snapshot of the affected resources, not just an id. You can act on most events without fetching anything.

Check the headers

Content-Type: application/json
Tmbilling-Signature: sha256=<hex>
Tmbilling-Event-Id: evt_8f14e45fea6c4a1f

Verify the signature on every request before you trust the body. Your URL is public, so anything can post to it. See Verifying signatures.

Build for three properties

PropertyWhat to do
Delivery is at-least-onceA retry after a network failure can redeliver an event you already handled. Record the Tmbilling-Event-Id values you have processed and skip repeats.
Events are not orderedinvoice_paid can arrive before invoice_posted. Compare occurred_at against what you have recorded rather than assuming arrival order.
New event types appeartmbilling adds event types within an API version. Log an event_type you don't recognise and skip it — never throw.

Answer fast, then do the work

Return a 2xx as soon as you have stored the event, and do the real work afterwards, out of band.

A handler that provisions an account, sends an email and only then answers will eventually time out and be redelivered — with the email sent twice.

Test and recover

CallWhat it does
POST /webhook_endpoints/{id}/testSends a sample payload, so you can verify your signature checking before anything real depends on it.
GET /webhook_endpoints/{id}/deliveriesLists every attempt, its response and its outcome.
POST /webhook_endpoints/{id}/deliveries/{del}/redeliverRedelivers one event after you fix your handler.
POST /webhook_endpoints/{id}/rotate_secretRotates the signing secret and returns the new one once.

You can also read events directly, which is your backstop when an endpoint stayed down longer than the retry schedule:

GET/v1/businesses/{biz}/events
GET/v1/events/{evt}

The second gives you an event with its per-endpoint delivery record — what tmbilling attempted, and what your server answered.

Copyright © 2026