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

# Settlements & Webhooks

> Receiving real-time settlement confirmations

Settlement status is available two ways: synchronously in the `POST /v1/execute` response, and asynchronously via webhook callback. Use webhooks to drive downstream processing without polling.

## Webhook setup

Provide your webhook endpoint URL during onboarding. Ratio sends HTTP POST requests to this URL when events occur.

| Configuration      | Description                                                       |
| ------------------ | ----------------------------------------------------------------- |
| **Webhook URL**    | Your HTTPS endpoint (e.g., `https://yourapp.com/ratio/callback`)  |
| **Webhook secret** | HMAC-SHA256 key for verifying callback authenticity               |
| **Events**         | Settlement confirmations, quote expirations, system state changes |

Your webhook endpoint must:

* Accept `POST` requests with `Content-Type: application/json`
* Return HTTP `200` within 5 seconds to acknowledge receipt
* Be idempotent — the same event may be delivered more than once

<Tip>
  Return `200` immediately on receipt, then process the event asynchronously. This prevents timeouts from triggering Ratio's retry logic.
</Tip>

## Webhook events

### `settlement.confirmed`

Fired when a swap settles on-chain. Includes full execution details.

```json theme={null}
{
  "event": "settlement.confirmed",
  "timestamp": "2026-02-27T10:00:32Z",
  "data": {
    "execution_id": "EX-9910-USD-IDR",
    "quote_id": "QT-8821-USD-IDR",
    "pair": "USD-IDR",
    "status": "SETTLED",
    "filled_rate": 16020.00,
    "source_amount": 50000,
    "destination_amount": 801000000,
    "tx_hash": "0xKAIA...ABC",
    "settled_at": "2026-02-27T10:00:32Z"
  }
}
```

### `settlement.failed`

Fired when a swap execution fails. This is rare and typically caused by an on-chain revert.

```json theme={null}
{
  "event": "settlement.failed",
  "timestamp": "2026-02-27T10:00:35Z",
  "data": {
    "execution_id": "EX-9911-USD-IDR",
    "quote_id": "QT-8822-USD-IDR",
    "pair": "USD-IDR",
    "status": "FAILED",
    "reason": "ON_CHAIN_REVERT"
  }
}
```

### `quote.expired`

Fired when a firm quote expires without being executed. Reserved inventory is released.

```json theme={null}
{
  "event": "quote.expired",
  "timestamp": "2026-02-27T10:01:15Z",
  "data": {
    "quote_id": "QT-8823-USD-IDR",
    "pair": "USD-IDR",
    "side": "BUY",
    "amount": 50000,
    "expiry_timestamp": "2026-02-27T10:01:15Z"
  }
}
```

### `system.state_change`

Fired when a corridor's operating state changes (for example, from `NORMAL` to `PROTECT`).

```json theme={null}
{
  "event": "system.state_change",
  "timestamp": "2026-02-27T10:05:00Z",
  "data": {
    "corridor": "MYR-IDR",
    "previous_state": "NORMAL",
    "new_state": "PROTECT",
    "allowed_directions": ["BUY", "SELL"]
  }
}
```

### Event types summary

| Event                  | Description                                                      |
| ---------------------- | ---------------------------------------------------------------- |
| `settlement.confirmed` | Swap has settled on-chain. Includes full execution details.      |
| `settlement.failed`    | Swap execution failed (rare — typically due to on-chain revert). |
| `quote.expired`        | A firm quote expired without being executed. Inventory released. |
| `system.state_change`  | A corridor's system state has changed.                           |

## Signature verification

Every webhook request includes a signature header:

```
X-Ratio-Signature: sha256=<hex_digest>
```

Verify the signature before processing any event:

1. Compute HMAC-SHA256 of the raw request body using your webhook secret.
2. Compare the computed digest with the value in the `X-Ratio-Signature` header.
3. Reject the request if the signatures do not match.

<Warning>
  Always verify the signature. Do not process webhook payloads that fail signature verification.
</Warning>

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
      expected = hmac.new(
          secret.encode(),
          body,
          hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(f"sha256={expected}", signature)
  ```

  ```javascript JavaScript theme={null}
  const crypto = require("crypto");

  function verifyWebhook(body, signature, secret) {
    const expected =
      "sha256=" +
      crypto.createHmac("sha256", secret).update(body).digest("hex");
    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signature)
    );
  }
  ```
</CodeGroup>

## Retry policy

If your endpoint does not return HTTP `200` within 5 seconds, Ratio retries with exponential backoff:

| Attempt   | Delay      |
| --------- | ---------- |
| 1st retry | 10 seconds |
| 2nd retry | 30 seconds |
| 3rd retry | 2 minutes  |
| 4th retry | 10 minutes |
| 5th retry | 1 hour     |

After 5 failed attempts, the webhook is marked as failed. You can query settlement status directly via `GET /v1/system/state` as a fallback.

## Best practices

* **Verify the signature** on every incoming request before processing the payload.
* **Make your handler idempotent.** The same event may be delivered more than once due to retries.
* **Return `200` immediately**, then process the event asynchronously to avoid triggering retries from slow processing.
* **Log the `execution_id`** from every `settlement.confirmed` event for reconciliation and support escalation.
