---
title: "Delivery and retries"
description: "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."
canonical_url: "https://twominutebilling.com/help/developers/webhooks/delivery"
---
# 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.

<table>
<thead>
  <tr>
    <th>
      Attempt
    </th>
    
    <th>
      Sent after the previous failure
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      1
    </td>
    
    <td>
      immediately
    </td>
  </tr>
  
  <tr>
    <td>
      2
    </td>
    
    <td>
      1 minute
    </td>
  </tr>
  
  <tr>
    <td>
      3
    </td>
    
    <td>
      5 minutes
    </td>
  </tr>
  
  <tr>
    <td>
      4
    </td>
    
    <td>
      30 minutes
    </td>
  </tr>
  
  <tr>
    <td>
      5
    </td>
    
    <td>
      2 hours
    </td>
  </tr>
  
  <tr>
    <td>
      6
    </td>
    
    <td>
      6 hours
    </td>
  </tr>
</tbody>
</table>

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

<warning title="tmbilling retries a 4xx too">

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.

</warning>

## 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

<endpoint method="GET" path="/v1/webhook_endpoints/{id}/deliveries">



</endpoint>

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.

<endpoint method="GET" path="/v1/events/{evt}">



</endpoint>

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

## Recover missed events

<table>
<thead>
  <tr>
    <th>
      Call
    </th>
    
    <th>
      Use it when
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        POST /webhook_endpoints/{id}/deliveries/{del}/redeliver
      </code>
    </td>
    
    <td>
      You fixed your handler and want one event back. It runs synchronously and returns the attempt, so you learn immediately whether the fix worked.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST /webhook_endpoints/{id}/redeliver_latest
      </code>
    </td>
    
    <td>
      You want a quick end-to-end check after a deploy.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET /businesses/{biz}/events
      </code>
    </td>
    
    <td>
      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.
    </td>
  </tr>
</tbody>
</table>

## Meet the endpoint requirements

<table>
<thead>
  <tr>
    <th>
      Requirement
    </th>
    
    <th>
      Why
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Use HTTPS in live mode
    </td>
    
    <td>
      The payload describes your customers and their money.
    </td>
  </tr>
  
  <tr>
    <td>
      Store, answer <code>
        2xx
      </code>
      
      , then work
    </td>
    
    <td>
      A slow handler gets retried while it is still working.
    </td>
  </tr>
  
  <tr>
    <td>
      Answer <code>
        2xx
      </code>
      
       for events you ignore
    </td>
    
    <td>
      An event type you don't handle is not an error. A <code>
        4xx
      </code>
      
       buys you six retries of something you were always going to discard.
    </td>
  </tr>
</tbody>
</table>

## Related

- [Verify webhook signatures](https://twominutebilling.com/help/developers/webhooks/signatures)
- [Webhook event reference](https://twominutebilling.com/help/developers/webhooks/events)


## Sitemap

See the full [sitemap](https://twominutebilling.com/sitemap.md) for all pages.
