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

# Webhooks

> Minisend POSTs a signed request to your server when a session completes, fails, or expires. Configure your webhook URL, verify signatures, and understand retries.

Minisend POSTs to your server when a session reaches a terminal state: `completed`, `failed`, or `expired`.

<Note>
  [Off-ramp orders](/offramp/overview) emit `offramp.*` events to the same URL with the same signature and retry behavior — payloads are documented in [off-ramp webhooks](/offramp/webhooks).
</Note>

## Setup

<Steps>
  <Step title="Add your URL">
    **Settings** → **Webhook URL** → paste your HTTPS endpoint (e.g., `https://yourbusiness.com/webhooks/minisend`).
  </Step>

  <Step title="Save">
    Minisend starts delivering events.
  </Step>

  <Step title="Copy your secret">
    Your `webhook_secret` is shown in **Settings**. Use it to verify incoming signatures.
  </Step>
</Steps>

<Warning>
  Always verify `X-Minisend-Signature` before processing. See [Verification](/webhooks/verification).
</Warning>

## Request format

| Header                 | Value                                |
| ---------------------- | ------------------------------------ |
| `Content-Type`         | `application/json`                   |
| `X-Minisend-Signature` | HMAC-SHA256 hex of the raw JSON body |
| `User-Agent`           | `Minisend-Webhooks/1.0`              |

Your endpoint must return a `2xx` within **10 seconds**.

## Sample payload

```json theme={null}
{
  "event": "checkout.completed",
  "session_id": "cs_7f8a9b2c-...",
  "external_id": "order-4821",
  "amount_usdc": 25.00,
  "amount_local": 3225.00,
  "currency": "KES",
  "exchange_rate": 129.00,
  "receipt": "SHQ1234ABC",
  "status": "completed",
  "completed_at": "2026-04-13T14:32:00Z",
  "created_at": "2026-04-13T14:00:00Z"
}
```

### Fields

<ResponseField name="event" type="string" required>
  `checkout.completed`, `checkout.failed`, or `checkout.expired`.
</ResponseField>

<ResponseField name="session_id" type="string" required>
  Use as an idempotency key.
</ResponseField>

<ResponseField name="external_id" type="string">
  Your reference, if set.
</ResponseField>

<ResponseField name="amount_usdc" type="number" required>
  USDC-equivalent amount.
</ResponseField>

<ResponseField name="amount_local" type="number">
  Net local currency after the 1% fee. `completed` only.
</ResponseField>

<ResponseField name="currency" type="string">
  Your payout currency: `KES`, `NGN`, `GHS`, or `UGX`. Always present.
</ResponseField>

<ResponseField name="exchange_rate" type="number">
  Local currency per 1 USDC at settlement. `completed` only.
</ResponseField>

<ResponseField name="receipt" type="string">
  Settlement receipt (e.g., M-Pesa code). `completed` only.
</ResponseField>

<ResponseField name="status" type="string" required>
  `completed`, `failed`, or `expired`.
</ResponseField>

<ResponseField name="completed_at" type="string">
  ISO 8601. `completed` only.
</ResponseField>

<ResponseField name="created_at" type="string" required>
  ISO 8601, when the session was created.
</ResponseField>

## Retry policy

Non-`2xx` or no response in 10s = retry. Up to **5 attempts** with exponential backoff:

| Attempt | Delay after previous |
| ------- | -------------------- |
| 1       | Immediate            |
| 2       | 30 seconds           |
| 3       | 2 minutes            |
| 4       | 8 minutes            |
| 5       | 32 minutes           |

Total window ≈ 42 minutes.

<Tip>
  Every delivery attempt is logged. Contact support with a `session_id` to inspect the log.
</Tip>
