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

# Webhook Error Handling

> Retry policy, timeout behavior, and best practices for handling webhook deliveries

# Webhook Error Handling

Vocobase automatically retries webhook deliveries that fail. This page explains the retry policy and best practices for building a reliable webhook handler.

## Retry policy

When a webhook delivery fails, Vocobase retries up to **4 times** (5 total delivery attempts):

| Attempt          | Delay before attempt |
| ---------------- | -------------------- |
| Initial delivery | Immediate            |
| 1st retry        | 5 minutes            |
| 2nd retry        | 30 minutes           |
| 3rd retry        | 2 hours              |
| 4th retry        | 8 hours              |

After all retries are exhausted, the webhook delivery is marked exhausted. The event data is still available via the API — use `GET /calls/{id}` to fetch call details, including live call status and final transcript/recording data when available.

## What counts as a failure

A webhook delivery is considered failed if:

* Your endpoint returns a **non-2xx status code** (e.g., 400, 500)
* The request **times out** (no response within 10 seconds)
* A **network error** occurs (DNS resolution failure, connection refused, TLS error)

A `2xx` response (200, 201, 202, 204, etc.) is treated as a successful delivery regardless of the response body.

## Timeout

Each webhook request has a **10-second timeout**. If your server does not respond within 10 seconds, the delivery is marked as failed and retried.

<Warning>
  If your webhook processing takes longer than 10 seconds, you will see repeated retries. Always return a response immediately and process the event asynchronously.
</Warning>

## Best practices

### Respond immediately

Return a `200 OK` response as soon as you receive the webhook. Do not perform heavy processing before responding.

```javascript theme={null}
// Good: respond first, process later
app.post("/webhooks/vocobase", (req, res) => {
  // Verify signature
  verifySignature(req);

  // Return immediately
  res.status(200).json({ received: true });

  // Process asynchronously
  processEventAsync(req.body);
});
```

```javascript theme={null}
// Bad: processing blocks the response
app.post("/webhooks/vocobase", async (req, res) => {
  verifySignature(req);

  // This might take > 10 seconds and cause a timeout
  await saveToDatabase(req.body);
  await sendSlackNotification(req.body);
  await updateCRM(req.body);

  res.status(200).json({ received: true });
});
```

### Handle duplicates (idempotency)

Due to retries, your endpoint may receive the same event more than once. Use an idempotency key that includes the event type and the fields that identify the transition:

* `session.completed`: use `session.completed:{session_id}`
* `call.status.updated`: use `call.status.updated:{call_id}:{status}:{occurred_at}`

```javascript theme={null}
app.post("/webhooks/vocobase", async (req, res) => {
  verifySignature(req);
  res.status(200).json({ received: true });

  const event = req.body;
  const data = event.data;
  const idempotencyKey =
    event.event === "call.status.updated"
      ? `${event.event}:${data.call_id}:${data.status}:${data.occurred_at}`
      : `${event.event}:${data.session_id}`;

  // Check if we already processed this event
  const exists = await db.processedEvents.findUnique({
    where: { idempotency_key: idempotencyKey }
  });
  if (exists) {
    console.log(`Duplicate webhook event ${idempotencyKey}, skipping`);
    return;
  }

  // Mark as processed before handling
  await db.processedEvents.create({ data: { idempotency_key: idempotencyKey } });

  // Now process the event
  await handleVocobaseWebhook(event);
});
```

### Verify signatures

Always verify the `X-Webhook-Signature` header in production to ensure the request is from Vocobase. See [Webhook Setup](/webhooks/setup) for implementation details.

### Use a message queue

For high-volume integrations, push webhook events into your message queue and process them with workers:

```javascript theme={null}
app.post("/webhooks/vocobase", (req, res) => {
  verifySignature(req);
  res.status(200).json({ received: true });

  // Push to queue for reliable processing
  queue.add("process-webhook", req.body);
});
```

### Monitor failures

If webhooks fail repeatedly, check:

1. **Your endpoint is reachable** — Verify the URL is publicly accessible and not behind a firewall
2. **TLS certificate is valid** — Expired or self-signed certificates cause connection failures
3. **Response time is under 10 seconds** — Profile your handler to find bottlenecks
4. **Your server is running** — Check logs for crashes or out-of-memory errors

### Fallback: poll the API

If you miss webhook events, you can always fetch call data directly:

```bash theme={null}
# List recent calls
curl -X GET "https://api.vocobase.com/api/v2/calls?limit=10" \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"

# Get full details for a specific call
curl -X GET "https://api.vocobase.com/api/v2/calls/c1234567-abcd-1234-abcd-123456789012" \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

## Next steps

<CardGroup cols={2}>
  <Card title="Webhook Payloads" icon="file-code" href="/webhooks/payloads">
    See the full payload structure for each event type.
  </Card>

  <Card title="Webhook Setup" icon="gear" href="/webhooks/setup">
    Configure webhook endpoints and verify signatures.
  </Card>
</CardGroup>
