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

# Webhooks

> Register outbound webhooks to receive real-time swap lifecycle notifications

## Overview

LeoKit webhooks push real-time event notifications to your server as swaps progress through their lifecycle. Discord and Slack webhook URLs are auto-detected and receive formatted embeds/blocks instead of raw JSON.

### Supported Events

| Event               | Description           | When it fires                                         |
| ------------------- | --------------------- | ----------------------------------------------------- |
| `quote.created`     | A quote was requested | After `/leokit/quote` responds and the quote is saved |
| `deposit.initiated` | A deposit was built   | After `/leokit/deposit` returns transaction data      |
| `swap.pending`      | Transaction submitted | After `/leokit/save-transaction` records the tx hash  |
| `swap.success`      | Swap completed        | Status polling detects completion                     |
| `swap.failed`       | Swap failed           | Status polling detects failure                        |
| `swap.refunded`     | Swap refunded         | Status polling detects refund                         |

### Webhook Types

| Type       | Auto-detected                         | Payload format  | Signing     |
| ---------- | ------------------------------------- | --------------- | ----------- |
| `standard` | Default                               | Raw JSON        | HMAC-SHA256 |
| `discord`  | `discord.com` / `discordapp.com` URLs | Discord embed   | None        |
| `slack`    | `hooks.slack.com` URLs                | Slack Block Kit | None        |

***

## POST /leokit/webhooks

Register a new webhook endpoint.

### Authentication

Requires `Api-Key` header.

### Request Body

```json theme={null}
{
  "url": "https://your-server.com/webhooks/leokit",
  "events": ["swap.success", "swap.failed", "swap.refunded"],
  "type": "standard"
}
```

| Field    | Type      | Required | Description                                                           |
| -------- | --------- | -------- | --------------------------------------------------------------------- |
| `url`    | string    | Yes      | HTTPS endpoint URL. Discord/Slack URLs are auto-detected.             |
| `events` | string\[] | No       | Events to subscribe to. Defaults to all 6 events.                     |
| `type`   | string    | No       | `standard`, `discord`, or `slack`. Auto-detected from URL if omitted. |

### Response

**Status Code:** `200 OK`

```json theme={null}
{
  "data": {
    "id": "01953c9a-...",
    "url": "https://your-server.com/webhooks/leokit",
    "events": ["swap.success", "swap.failed", "swap.refunded"],
    "type": "standard",
    "is_active": true,
    "created_at": "2026-02-07T12:00:00.000Z",
    "secret": "a1b2c3d4e5f6..."
  }
}
```

**Important:** The `secret` is only returned on creation. Save it immediately — you'll need it to verify webhook signatures.

***

## GET /leokit/webhooks

List all webhooks for the authenticated client.

### Authentication

Requires `Api-Key` header.

### Response

```json theme={null}
{
  "data": {
    "webhooks": [
      {
        "id": "01953c9a-...",
        "url": "https://your-server.com/webhooks/leokit",
        "events": ["swap.success", "swap.failed", "swap.refunded"],
        "type": "standard",
        "is_active": true,
        "created_at": "2026-02-07T12:00:00.000Z",
        "updated_at": "2026-02-07T12:00:00.000Z"
      }
    ]
  }
}
```

***

## DELETE /leokit/webhooks?id={webhook_id}

Delete a webhook by ID.

### Authentication

Requires `Api-Key` header.

### Query Parameters

| Parameter | Type   | Required | Description          |
| --------- | ------ | -------- | -------------------- |
| `id`      | string | Yes      | Webhook ID to delete |

### Response

```json theme={null}
{
  "data": {
    "deleted": true
  }
}
```

***

## Webhook Payload (Standard)

Standard webhooks receive a JSON payload with HMAC-SHA256 signature headers.

### Headers

| Header                 | Description                           |
| ---------------------- | ------------------------------------- |
| `X-LeoKit-Event`       | Event type (e.g., `swap.success`)     |
| `X-LeoKit-Signature`   | HMAC-SHA256 signature: `sha256=<hex>` |
| `X-LeoKit-Delivery-ID` | Unique delivery ID                    |
| `X-LeoKit-Timestamp`   | Unix timestamp (seconds)              |

### Body

```json theme={null}
{
  "event": "swap.success",
  "timestamp": "2026-02-07T12:30:00.000Z",
  "data": {
    "quote_id": "01953c9a-...",
    "protocol": "chainflip",
    "status": "completed",
    "from_asset": "ETH.USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "to_asset": "BTC.BTC",
    "from_address": "0x1234...",
    "to_address": "bc1q...",
    "tx_hash": "0xabc...",
    "scanner_url": "https://etherscan.io/tx/0xabc..."
  }
}
```

### Verifying Signatures

Compute HMAC-SHA256 of the raw request body using your webhook `secret`, then compare to the `X-LeoKit-Signature` header:

```javascript theme={null}
import crypto from "crypto";

function verifyWebhook(body, secret, signature) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}
```

***

## Delivery & Retry

* **Timeout:** 10 seconds per delivery attempt
* **Max attempts:** 5
* **Backoff schedule:** 30s, 1m, 5m, 15m, 1h
* **Final state:** Marked as `failed` after 5 failed attempts

Deliveries are processed by a background worker on the leader instance.

## Rate Limits

* **Webhooks per client:** 20 active webhooks max
* **Events per webhook:** No per-event rate limit; high-volume clients may see deliveries batched within the same backoff window
* **TLS required:** `url` must be `https://` (Discord/Slack URLs are also enforced as HTTPS)

***

## Discord Webhooks

Discord webhook URLs (`discord.com/api/webhooks/...`) automatically receive formatted embed payloads with color-coded event types:

* Green: `swap.success`
* Red: `swap.failed`
* Yellow: `swap.refunded`
* Blue: `quote.created`, `deposit.initiated`, `swap.pending`

No signature verification — Discord handles authentication via the webhook URL secret.

## Slack Webhooks

Slack incoming webhook URLs (`hooks.slack.com/services/...`) automatically receive Block Kit formatted payloads with header blocks, mrkdwn fields, and contextual metadata.

***

## Examples

### Register a Discord webhook

```bash theme={null}
curl -X POST https://api.leokit.dev/leokit/webhooks \
  -H "Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://discord.com/api/webhooks/123/abc"}'
```

Response will show `"type": "discord"` (auto-detected).

### Register for specific events only

```bash theme={null}
curl -X POST https://api.leokit.dev/leokit/webhooks \
  -H "Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/hook",
    "events": ["swap.success", "swap.failed"]
  }'
```

### List webhooks

```bash theme={null}
curl https://api.leokit.dev/leokit/webhooks \
  -H "Api-Key: YOUR_API_KEY"
```

### Delete a webhook

```bash theme={null}
curl -X DELETE "https://api.leokit.dev/leokit/webhooks?id=01953c9a-..." \
  -H "Api-Key: YOUR_API_KEY"
```
