> ## 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.

# Off-ramp webhooks

> Payload schemas for offramp.completed, offramp.failed, and offramp.expired, delivered to your webhook URL and signed with the same X-Minisend-Signature header.

Off-ramp events are delivered to the same webhook URL you configure in [Settings](/dashboard/settings), signed with the same secret and `X-Minisend-Signature` header as checkout events. [Signature verification](/webhooks/verification) is identical, and failed deliveries retry up to 5 times with exponential backoff.

| Event               | Fires when                        | Terminal status |
| ------------------- | --------------------------------- | --------------- |
| `offramp.completed` | Payout delivered to the recipient | `completed`     |
| `offramp.failed`    | Payout failed after your deposit  | `failed`        |
| `offramp.expired`   | No deposit before `expires_at`    | `expired`       |

***

## offramp.completed

```json theme={null}
{
  "event": "offramp.completed",
  "order_id": "9b2f6c1e-...",
  "external_reference": "payout-8412",
  "status": "completed",
  "amount_usdc": 10,
  "amount_local": 1294,
  "payout_currency": "KES",
  "exchange_rate": 129.45,
  "fee": 13,
  "recipient_account_name": "Jane Wanjiku",
  "settlement_receipt": "SHQ1234ABC",
  "completed_at": "2026-07-05T12:07:41Z",
  "created_at": "2026-07-05T12:00:03Z"
}
```

<ResponseField name="event" type="string" required>
  Always `offramp.completed`.
</ResponseField>

<ResponseField name="order_id" type="string" required>
  Use as an idempotency key. Minisend may redeliver if your server timed out.
</ResponseField>

<ResponseField name="external_reference" type="string">
  Your `reference`, if set at creation.
</ResponseField>

<ResponseField name="amount_usdc" type="number" required>
  The order's USDC amount.
</ResponseField>

<ResponseField name="amount_local" type="number" required>
  Gross local amount at the executed rate. The recipient received `amount_local − fee`.
</ResponseField>

<ResponseField name="payout_currency" type="string" required>
  `KES`, `NGN`, `GHS`, or `UGX`.
</ResponseField>

<ResponseField name="exchange_rate" type="number" required>
  Local currency per 1 USDC, as executed.
</ResponseField>

<ResponseField name="fee" type="number">
  Minisend fee in local units. `0` for NGN.
</ResponseField>

<ResponseField name="recipient_account_name" type="string" required />

<ResponseField name="settlement_receipt" type="string" required>
  Payout receipt: an M-Pesa code for KES mobile payouts, an on-chain settlement hash for NGN.
</ResponseField>

<ResponseField name="completed_at" type="string" required>
  ISO 8601.
</ResponseField>

<ResponseField name="created_at" type="string" required>
  ISO 8601.
</ResponseField>

***

## offramp.failed

The payout could not complete after your deposit.

```json theme={null}
{
  "event": "offramp.failed",
  "order_id": "9b2f6c1e-...",
  "external_reference": "payout-8412",
  "status": "failed",
  "amount_usdc": 10,
  "amount_local": 1294,
  "payout_currency": "KES",
  "exchange_rate": 129.45,
  "fee": 13,
  "recipient_account_name": "Jane Wanjiku",
  "created_at": "2026-07-05T12:00:03Z"
}
```

<Note>
  NGN deposits are refunded automatically to the order's `refund_address`. For KES, GHS, and UGX failures, contact [support](https://t.me/minisendapp) with the `order_id`. Funds are not lost.
</Note>

***

## offramp.expired

No deposit arrived before `expires_at`. Nothing was sent and nothing is owed. Create a new order to retry.

```json theme={null}
{
  "event": "offramp.expired",
  "order_id": "9b2f6c1e-...",
  "external_reference": "payout-8412",
  "status": "expired",
  "amount_usdc": 10,
  "payout_currency": "KES",
  "recipient_account_name": "Jane Wanjiku",
  "created_at": "2026-07-05T12:00:03Z"
}
```

<Warning>
  If you sent USDC but the order still expired (e.g. the transfer confirmed after the window, or you never submitted the hash), contact [support](https://t.me/minisendapp) with the `order_id` and transaction hash.
</Warning>

***

## Handling pattern

Same as checkout webhooks: respond `200` immediately, process async, and dedupe on `order_id`.

```javascript theme={null}
app.post('/webhooks/minisend', (req, res) => {
  res.status(200).send('OK');

  const payload = req.body;
  if (payload.event?.startsWith('offramp.')) {
    processOfframpEvent(payload); // dedupe on payload.order_id
  } else {
    processCheckoutEvent(payload); // dedupe on payload.session_id
  }
});
```
