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

# Retry Policy

> How Kallglot retries failed webhook deliveries

# Webhook Retry Policy

When a webhook delivery fails, Kallglot automatically retries the request with exponential backoff.

## What Triggers a Retry

A webhook delivery is considered failed if:

* Your endpoint returns a non-2xx HTTP status code
* Your endpoint doesn't respond within 30 seconds
* The connection cannot be established
* SSL/TLS handshake fails

## Retry Schedule

Kallglot uses exponential backoff with jitter:

| Attempt | Delay        | Total Time Elapsed |
| ------- | ------------ | ------------------ |
| 1       | Immediate    | 0                  |
| 2       | \~1 minute   | 1 minute           |
| 3       | \~5 minutes  | 6 minutes          |
| 4       | \~30 minutes | 36 minutes         |
| 5       | \~2 hours    | 2.5 hours          |
| 6       | \~5 hours    | 7.5 hours          |
| 7       | \~10 hours   | 17.5 hours         |
| 8       | \~24 hours   | 41.5 hours         |

After 8 failed attempts (\~41 hours), the webhook is marked as permanently failed.

## Retry Headers

Retry attempts include additional headers:

| Header                        | Description                                 |
| ----------------------------- | ------------------------------------------- |
| `Kallglot-Webhook-Id`         | Unique ID for this delivery                 |
| `Kallglot-Retry-Count`        | Current retry attempt (0 for first attempt) |
| `Kallglot-Original-Timestamp` | When the event was first created            |

```http theme={null}
POST /webhooks/kallglot HTTP/1.1
Host: your-app.com
Content-Type: application/json
Kallglot-Signature: t=1711454400,v1=abc123...
Kallglot-Webhook-Id: wh_del_01HXYZ
Kallglot-Retry-Count: 2
Kallglot-Original-Timestamp: 1711454000
```

## Idempotency

Because webhooks may be delivered multiple times, your handler must be idempotent. Use the `id` field to deduplicate:

```javascript theme={null}
async function handleWebhook(event) {
  // Check if already processed
  const processed = await db.webhookEvents.findOne({ eventId: event.id });
  if (processed) {
    console.log(`Event ${event.id} already processed, skipping`);
    return;
  }

  // Process the event
  await processEvent(event);

  // Mark as processed
  await db.webhookEvents.insert({
    eventId: event.id,
    processedAt: new Date()
  });
}
```

## Monitoring Deliveries

### Developer Portal

View delivery status in the Developer Portal:

1. Go to **Webhooks** > select your endpoint
2. Click **Delivery History**
3. View status, response, and retry history for each event

### Delivery Statuses

| Status      | Description                           |
| ----------- | ------------------------------------- |
| `pending`   | Waiting to be sent                    |
| `delivered` | Successfully delivered (2xx response) |
| `retrying`  | Failed, retry scheduled               |
| `failed`    | All retries exhausted                 |

### Webhook Events

Subscribe to meta-events about your webhooks:

```json theme={null}
{
  "type": "webhook_endpoint.disabled",
  "data": {
    "endpoint_id": "wh_01ABC",
    "url": "https://your-app.com/webhooks",
    "reason": "too_many_failures",
    "failed_events": 100
  }
}
```

## Automatic Disabling

Endpoints are automatically disabled after:

* 100 consecutive failed deliveries, or
* 7 days of continuous failures

When disabled, you'll receive an email notification and a `webhook_endpoint.disabled` event (to other healthy endpoints).

To re-enable:

1. Fix the underlying issue
2. Go to **Developer Portal** > **Webhooks**
3. Select the disabled endpoint
4. Click **Re-enable**

## Best Practices

<AccordionGroup>
  <Accordion title="Return 200 quickly">
    Acknowledge receipt immediately and process asynchronously:

    ```javascript theme={null}
    app.post('/webhooks', (req, res) => {
      // Verify signature
      // ...

      // Queue for processing
      queue.add(req.body);

      // Return immediately
      res.status(200).send('OK');
    });
    ```
  </Accordion>

  <Accordion title="Use a message queue">
    For reliability, enqueue webhooks in **your own** workload queue (for example SQS or RabbitMQ):

    ```javascript theme={null}
    app.post('/webhooks', async (req, res) => {
      await sqs.sendMessage({
        QueueUrl: WEBHOOK_QUEUE_URL,
        MessageBody: JSON.stringify(req.body),
        MessageDeduplicationId: req.body.id
      });

      res.status(200).send('OK');
    });
    ```
  </Accordion>

  <Accordion title="Handle out-of-order delivery">
    Events may arrive out of order. Use timestamps to handle this:

    ```javascript theme={null}
    async function handleSessionEvent(event) {
      const session = await db.sessions.findOne({ id: event.data.id });

      // Only process if this event is newer
      if (session && event.created_at <= session.lastEventAt) {
        console.log('Skipping stale event');
        return;
      }

      await processEvent(event);
    }
    ```
  </Accordion>

  <Accordion title="Set up alerting">
    Treat repeated `4xx`/`5xx` responses to Kallglot retries as a paging incident. Most teams wire alerts from their own metrics (log counts, health checks) or from periodically reviewing **Webhooks → Delivery History** in the Developer Portal.
  </Accordion>
</AccordionGroup>

## Manual Retry

Retry failed deliveries from the Developer Portal:

1. Go to **Webhooks** > select endpoint > **Delivery History**
2. Find the failed event
3. Click **Retry**

<Note>
  There is no documented public HTTP endpoint for delivery retries; use the portal unless your account team provides a private automation.
</Note>

## Event Expiration

Events are retained for 30 days. After that:

* Events cannot be viewed in the Developer Portal
* Manual retries are no longer possible
* The event data is permanently deleted
