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

# Webhook Retries

> How Kyren Pay retries failed webhook deliveries

If your webhook endpoint doesn't respond with a `2xx` status code, Kyren Pay will automatically retry the delivery.

## Retry Schedule

Kyren Pay retries failed webhooks up to **7 times**:

| Attempt   | Delay After Failure |
| --------- | ------------------- |
| 1st retry | 30 seconds          |
| 2nd retry | 30 seconds          |
| 3rd retry | 1 minute            |
| 4th retry | 1 minute            |
| 5th retry | 30 minutes          |
| 6th retry | 30 minutes          |
| 7th retry | 30 minutes          |

After 7 failed attempts, the webhook is marked as **failed** and no further retries are attempted.

## What Counts as a Failure

A webhook delivery is considered failed if:

* Your endpoint returns a non-`2xx` HTTP status code (e.g., `400`, `500`)
* Your endpoint doesn't respond within **30 seconds**
* The connection to your server fails (DNS error, connection refused, timeout)

<Info>
  A `2xx` response (e.g., `200`, `201`, `204`) is considered a successful delivery regardless of the response body.
</Info>

## Handling Retries

Since webhooks may be retried, your endpoint should be **idempotent** — processing the same event multiple times should produce the same result.

### Use Event IDs for Deduplication

Every webhook event has a unique `id` field. Store processed event IDs and skip duplicates:

```javascript theme={null}
const processedEvents = new Set(); // Use a database in production

app.post('/webhooks/kyren', (req, res) => {
  // ... verify signature ...

  const event = JSON.parse(req.body.toString());

  // Check if we've already processed this event
  if (processedEvents.has(event.id)) {
    console.log(`Duplicate event ${event.id}, skipping`);
    return res.status(200).send('OK');
  }

  // Process the event
  processEvent(event);

  // Mark as processed
  processedEvents.add(event.id);

  res.status(200).send('OK');
});
```

<Tip>
  In production, store processed event IDs in a database (e.g., Redis or your primary database) rather than in-memory.
</Tip>

### Return 200 Quickly

Return a `200` response as soon as you receive and validate the webhook. If processing takes a long time, do it asynchronously:

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

  const event = JSON.parse(req.body.toString());

  // Respond immediately
  res.status(200).send('OK');

  // Process asynchronously
  processEventAsync(event).catch(err => {
    console.error('Failed to process event:', err);
  });
});
```

## Monitoring Webhook Deliveries

When troubleshooting delivery, compare Kyren Webhook events with your own server logs. Record:

* Your configured Webhook URL
* The event type and order number
* The timestamp when your server received the request
* The response code or error your server returned

If you need Kyren to check delivery attempts, contact support with those details.

## Troubleshooting

| Problem                      | Solution                                                         |
| ---------------------------- | ---------------------------------------------------------------- |
| Not receiving webhooks       | Check your webhook URL is correct and publicly accessible        |
| Signature verification fails | Ensure you're using the raw request body (not parsed JSON)       |
| Frequent timeouts            | Respond with `200` immediately and process events asynchronously |
| Duplicate events             | Implement idempotency using the event `id` field                 |
