> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bindbee.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Delivery and retries

> Learn when Bindbee retries a webhook and how to process each event once.

Bindbee sends each event as a `POST` to your URL and waits up to 10 seconds for a response. A `2xx` means delivered. Anything else is retried or given up on, depending on the status. These rules apply to version 1 and version 2 webhooks alike.

## Retries

| | |
| - | - |
| Attempts | Up to 4: the first, then 3 retries |
| Wait between attempts | 1, 2 and 4 seconds |
| Timeout per attempt | 10 seconds |
| Retried | `408`, `429`, any `5xx`, a timeout, and a network failure such as DNS, TLS or a refused connection |
| Not retried | Any other `4xx`, and any `3xx`. Redirects are not followed |

A `Retry-After` header on a retried response can lengthen a wait to at most 5 seconds. Every attempt for an event finishes within about a minute.

<Warning>
  After the last attempt, the event is not sent again. Bindbee has no redelivery
  and no replay, so an event your endpoint missed has to be recovered by reading
  the data - see [Recover missed events](#recover-missed-events).
</Warning>

A failing endpoint stays enabled. Bindbee keeps sending new events to it, so it recovers on its own once it answers again.

## What your endpoint should return

**Respond `200` as soon as the signature verifies, then process the event.** A handler that does its work before responding runs into the 10-second timeout under load, and each timeout spends an attempt.

| Your situation | Return | Bindbee |
| - | - | - |
| Verified and queued for processing | `200` | Marks the event delivered |
| Signature invalid | `401` | Gives up. It won't verify on retry either |
| Temporarily unable to accept | `503`, or `429` with `Retry-After` | Retries |
| An event you don't handle | `200` | Marks it delivered. Unsubscribe from it to stop receiving it |

A `4xx` is final, so don't return one for a temporary problem. A `400` from a validation layer that rejects an unfamiliar field loses that event for good.

## Deduplicate events

Your endpoint can receive an event more than once, for example when it processes the event but times out before responding, and Bindbee retries. Every copy carries the same `id` in the body and the same `webhook-id` header.

Store each `id` you process, and skip an event whose `id` you've already stored. The `id` is the same on every webhook that receives the event, so the same check collapses one event arriving at two of your endpoints.

Version 1 events carry no ID. For those, `sync.sync_id` combined with the event name identifies an event within a sync.

## Order events

Events can arrive out of order across syncs and connectors, and your own queue can reorder them. Use `sequence` to put them back in order.

`sequence` is per connector, and a later event always has a higher number. Expect gaps. Numbers are shared across every webhook in your organization, so events you're not subscribed to still take one. Compare numbers rather than counting them.

```python theme={null}
last = last_sequence.get(event["connector"]["id"], 0)
if event["sequence"] <= last:
    return  # older than state you already hold
last_sequence[event["connector"]["id"]] = event["sequence"]
```

Skipping an older event is safe when you re-read records rather than applying the event's content. The IDs tell you what to read, and the read returns current data.

## Recover missed events

An event can be lost when your endpoint is down for longer than the retries last, or when a webhook is disabled. Recover the data from the API:

1. Find where the gap starts, from the [delivery logs](#monitor-deliveries) or from the last `connector.sync.completed` you processed for each connector.
2. Read each model you subscribe to with `modified_after` set to the start of the gap - see [modified\_after](/guides/reading-writing/reading-data/modified-after).

Run the same read on a schedule as a reconciliation sweep, and a missed event costs you a delay rather than a record - see [Syncing](/guides/reading-writing/syncing).

## Monitor deliveries

Each delivery log entry records one event sent to one webhook, with the status of the final attempt. Individual retries aren't logged separately.

| `response_code` | Meaning |
| - | - |
| `2xx` | Delivered |
| `3xx`, `4xx`, `5xx` | Your endpoint's final answer |
| `518` | No response: DNS, TLS, a refused connection or a timeout |
| `520` | An unknown error before a response was received |

### In the dashboard

Delivery logs are on the [Webhooks tab](https://app.bindbee.dev/logs/webhooks) under **Logs**, and on each webhook's own page. Click an entry to see the request Bindbee sent and the response your endpoint returned.

### Through the API

[List your webhooks](/api-reference/webhooks/list-webhooks) to get their IDs, enabled state and when each last fired:

```bash theme={null}
curl -G https://api.bindbee.dev/api/v1/webhooks \
  -H "Authorization: Bearer <BINDBEE_API_KEY>" \
  -d is_active=true
```

[List deliveries](/api-reference/webhooks/list-webhook-logs) with filters. Polling this with `errors_only=true` catches an endpoint that has stopped accepting deliveries:

```bash theme={null}
curl -G https://api.bindbee.dev/api/v1/webhooks/logs \
  -H "Authorization: Bearer <BINDBEE_API_KEY>" \
  -d errors_only=true \
  -d created_at_from=2026-09-01T00:00:00Z
```

| Parameter | Purpose |
| - | - |
| `webhook_id` | Deliveries for one webhook |
| `connector_id` | Deliveries triggered for one connector |
| `webhook_action` | Comma-separated event names, up to 50, for example `connector.sync.failed,hris.employee.updated` |
| `response_code` | Comma-separated status codes, for example `500,518` |
| `created_at_from`, `created_at_to` | ISO 8601 bounds, read as UTC when no offset is given |
| `errors_only` | When `true`, only deliveries whose final status was `400` or above |

[Get one delivery](/api-reference/webhooks/get-webhook-log-detail) for the full request and response:

```bash theme={null}
curl https://api.bindbee.dev/api/v1/webhooks/logs/<LOG_ID> \
  -H "Authorization: Bearer <BINDBEE_API_KEY>"
```

`last_triggered_at` on a webhook updates on every attempt, including failed ones. It shows that Bindbee is sending. The logs show whether your endpoint is accepting.

## Related

* [Verify webhook signatures](/guides/webhooks/verify-signatures) - the check that precedes your `200`
* [Webhook payload](/guides/webhooks/payload) - where `id` and `sequence` sit
* [Webhook events](/guides/webhooks/events#order-within-a-sync) - the order events of one sync are sent in
* [Sync status](/guides/troubleshooting/sync-status) - catching a connector that stopped syncing, which no webhook tells you


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.