# Webhooks

Real-time notifications for order and KYC status changes. The system sends a signed HTTP POST to your configured webhook URL on every relevant event.

**Event model**

Two event types are delivered:

| Event type             | Fired when                                        |
| ---------------------- | ------------------------------------------------- |
| `order.status.changed` | An order transitions to a new status              |
| `user.kyc.updated`     | A user's KYC verification is approved or declined |

All events share the same envelope (`event_id`, `event_type`, `created_at`, `data`) and the same signature scheme. The structure of the `data` object differs per event type.

**Payload shape — `order.status.changed`**

```json
{
  "event_id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "event_type": "order.status.changed",
  "created_at": "2026-02-01T12:00:05Z",
  "data": {
    "order_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "status": "crypto_received",
    "user_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "crypto_amount": "101.500000",
    "crypto_currency": "USDT",
    "fiat_amount": "152880.00",
    "fiat_currency": "NGN",
    "provider": "p2p",
    "payment_details_id": "987"
  }
}
```

**Payload shape — `user.kyc.updated`**

Fired when a user's KYC status transitions to `verified` or `rejected`. Use this event to unlock order creation for the user on your side.

```json
{
  "event_id": "evt_2d8418dd-e3a8-4463-a11e-6b54f354ca90",
  "event_type": "user.kyc.updated",
  "created_at": "2026-03-10T11:37:42Z",
  "data": {
    "user_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "kyc_status": "VERIFIED"
  }
}
```

When KYC is declined, `rejection_reasons` is included:

```json
{
  "event_id": "evt_fc75484d-b374-4d05-b54e-820f3dd80e6d",
  "event_type": "user.kyc.updated",
  "created_at": "2026-03-10T12:00:00Z",
  "data": {
    "user_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "kyc_status": "REJECTED",
    "rejection_reasons": ["Document expired", "Photo quality low"]
  }
}
```

`kyc_status` values: `VERIFIED` | `REJECTED`

**Signature verification**

Every delivery includes two headers for HMAC-SHA256 verification:

* `X-Unigox-Signature: sha256=<hex>` — HMAC of `"<timestamp>.<raw_body>"`
* `X-Unigox-Timestamp: <unix_seconds>`

Verification pseudocode:

```
expected = HMAC-SHA256(webhook_secret, "<timestamp>.<raw_body>")
valid    = constant_time_compare(expected, signature_from_header)
```

**Delivery guarantees**

* At-least-once delivery. Your endpoint may receive the same event more than once.
* De-duplicate by `event_id`.
* Respond with any 2xx status within 10 seconds to acknowledge receipt.

**Retry policy**

Failed deliveries (non-2xx or timeout) are retried with exponential backoff: 1 min → 5 min → 15 min → 1 h → 6 h → 12 h → 24 h (max 10 attempts over \~3.5 days).

**Partner-facing statuses** (`order.status.changed`)

| Status                                   | Description                                                      |
| ---------------------------------------- | ---------------------------------------------------------------- |
| `created`                                | Order created                                                    |
| `awaiting_liquidity_provider`            | Waiting for vendor match                                         |
| `awaiting_crypto_transfer_authorization` | Partner must authorize crypto transfer                           |
| `crypto_received`                        | Crypto received in escrow                                        |
| `fiat_payment_started`                   | Buyer submitted fiat payment proof                               |
| `fiat_payment_review_started`            | Payment proof under admin review                                 |
| `awaiting_fiat_received_confirmation`    | Proof approved, awaiting seller confirmation                     |
| `completed`                              | Order completed, crypto released                                 |
| `cancelled`                              | Order cancelled or payment declined                              |
| `failed`                                 | Escrow error, or fiat-anchored safety bound exceeded (see below) |
| `dispute_started`                        | Dispute opened                                                   |

**Fiat-anchored safety bound failure**

For fiat-anchored off-ramp orders (`anchor_type: "fiat"`), if Switch quotes a crypto cost exceeding 1.20× the expected amount at the time of settlement, the order moves to `failed` status asynchronously (not during `/initiate`). The deposit is automatically refunded from escrow back to the partner wallet. Create a new quote to retry the trade flow.

## Get webhook config

> Returns the current webhook configuration for your partner account,\
> including the URL and whether the webhook is enabled.<br>

````json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Webhooks","description":"Real-time notifications for order and KYC status changes.\nThe system sends a signed HTTP POST to your configured webhook URL on every relevant event.\n\n**Event model**\n\nTwo event types are delivered:\n\n| Event type | Fired when |\n|---|---|\n| `order.status.changed` | An order transitions to a new status |\n| `user.kyc.updated` | A user's KYC verification is approved or declined |\n\nAll events share the same envelope (`event_id`, `event_type`, `created_at`, `data`)\nand the same signature scheme. The structure of the `data` object differs per event type.\n\n**Payload shape — `order.status.changed`**\n\n```json\n{\n  \"event_id\": \"evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n  \"event_type\": \"order.status.changed\",\n  \"created_at\": \"2026-02-01T12:00:05Z\",\n  \"data\": {\n    \"order_id\": \"b2c3d4e5-f6a7-8901-bcde-f12345678901\",\n    \"status\": \"crypto_received\",\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"crypto_amount\": \"101.500000\",\n    \"crypto_currency\": \"USDT\",\n    \"fiat_amount\": \"152880.00\",\n    \"fiat_currency\": \"NGN\",\n    \"provider\": \"p2p\",\n    \"payment_details_id\": \"987\"\n  }\n}\n```\n\n**Payload shape — `user.kyc.updated`**\n\nFired when a user's KYC status transitions to `verified` or `rejected`.\nUse this event to unlock order creation for the user on your side.\n\n```json\n{\n  \"event_id\": \"evt_2d8418dd-e3a8-4463-a11e-6b54f354ca90\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T11:37:42Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"VERIFIED\"\n  }\n}\n```\n\nWhen KYC is declined, `rejection_reasons` is included:\n\n```json\n{\n  \"event_id\": \"evt_fc75484d-b374-4d05-b54e-820f3dd80e6d\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:00:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"REJECTED\",\n    \"rejection_reasons\": [\"Document expired\", \"Photo quality low\"]\n  }\n}\n```\n\n`kyc_status` values: `VERIFIED` | `REJECTED`\n\n**Signature verification**\n\nEvery delivery includes two headers for HMAC-SHA256 verification:\n- `X-Unigox-Signature: sha256=<hex>` — HMAC of `\"<timestamp>.<raw_body>\"`\n- `X-Unigox-Timestamp: <unix_seconds>`\n\nVerification pseudocode:\n```\nexpected = HMAC-SHA256(webhook_secret, \"<timestamp>.<raw_body>\")\nvalid    = constant_time_compare(expected, signature_from_header)\n```\n\n**Delivery guarantees**\n\n- At-least-once delivery. Your endpoint may receive the same event more than once.\n- De-duplicate by `event_id`.\n- Respond with any 2xx status within 10 seconds to acknowledge receipt.\n\n**Retry policy**\n\nFailed deliveries (non-2xx or timeout) are retried with exponential backoff:\n1 min → 5 min → 15 min → 1 h → 6 h → 12 h → 24 h (max 10 attempts over ~3.5 days).\n\n**Partner-facing statuses** (`order.status.changed`)\n\n| Status | Description |\n|---|---|\n| `created` | Order created |\n| `awaiting_liquidity_provider` | Waiting for vendor match |\n| `awaiting_crypto_transfer_authorization` | Partner must authorize crypto transfer |\n| `crypto_received` | Crypto received in escrow |\n| `fiat_payment_started` | Buyer submitted fiat payment proof |\n| `fiat_payment_review_started` | Payment proof under admin review |\n| `awaiting_fiat_received_confirmation` | Proof approved, awaiting seller confirmation |\n| `completed` | Order completed, crypto released |\n| `cancelled` | Order cancelled or payment declined |\n| `failed` | Escrow error, or fiat-anchored safety bound exceeded (see below) |\n| `dispute_started` | Dispute opened |\n\n**Fiat-anchored safety bound failure**\n\nFor fiat-anchored off-ramp orders (`anchor_type: \"fiat\"`), if Switch quotes a crypto cost exceeding\n1.20× the expected amount at the time of settlement, the order moves to `failed` status asynchronously\n(not during `/initiate`). The deposit is automatically refunded from escrow back to the partner wallet.\nCreate a new quote to retry the trade flow.\n"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"WebhookConfigResponse":{"type":"object","properties":{"url":{"type":"string","nullable":true,"description":"Currently configured webhook URL (null if not set)"},"enabled":{"type":"boolean","description":"Whether webhook delivery is active"}}}}},"paths":{"/api/v1/partner/webhooks":{"get":{"tags":["Webhooks"],"summary":"Get webhook config","description":"Returns the current webhook configuration for your partner account,\nincluding the URL and whether the webhook is enabled.\n","operationId":"getWebhook","responses":{"200":{"description":"Webhook configuration retrieved","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookConfigResponse"}}}]}}}},"401":{"description":"Unauthorized — invalid or missing API key"}}}}}}
````

## Register webhook URL

> Register or update the webhook URL for your partner account. The URL must use HTTPS.\
> Once registered, the webhook is automatically enabled and will start receiving order status events.<br>

````json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Webhooks","description":"Real-time notifications for order and KYC status changes.\nThe system sends a signed HTTP POST to your configured webhook URL on every relevant event.\n\n**Event model**\n\nTwo event types are delivered:\n\n| Event type | Fired when |\n|---|---|\n| `order.status.changed` | An order transitions to a new status |\n| `user.kyc.updated` | A user's KYC verification is approved or declined |\n\nAll events share the same envelope (`event_id`, `event_type`, `created_at`, `data`)\nand the same signature scheme. The structure of the `data` object differs per event type.\n\n**Payload shape — `order.status.changed`**\n\n```json\n{\n  \"event_id\": \"evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n  \"event_type\": \"order.status.changed\",\n  \"created_at\": \"2026-02-01T12:00:05Z\",\n  \"data\": {\n    \"order_id\": \"b2c3d4e5-f6a7-8901-bcde-f12345678901\",\n    \"status\": \"crypto_received\",\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"crypto_amount\": \"101.500000\",\n    \"crypto_currency\": \"USDT\",\n    \"fiat_amount\": \"152880.00\",\n    \"fiat_currency\": \"NGN\",\n    \"provider\": \"p2p\",\n    \"payment_details_id\": \"987\"\n  }\n}\n```\n\n**Payload shape — `user.kyc.updated`**\n\nFired when a user's KYC status transitions to `verified` or `rejected`.\nUse this event to unlock order creation for the user on your side.\n\n```json\n{\n  \"event_id\": \"evt_2d8418dd-e3a8-4463-a11e-6b54f354ca90\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T11:37:42Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"VERIFIED\"\n  }\n}\n```\n\nWhen KYC is declined, `rejection_reasons` is included:\n\n```json\n{\n  \"event_id\": \"evt_fc75484d-b374-4d05-b54e-820f3dd80e6d\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:00:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"REJECTED\",\n    \"rejection_reasons\": [\"Document expired\", \"Photo quality low\"]\n  }\n}\n```\n\n`kyc_status` values: `VERIFIED` | `REJECTED`\n\n**Signature verification**\n\nEvery delivery includes two headers for HMAC-SHA256 verification:\n- `X-Unigox-Signature: sha256=<hex>` — HMAC of `\"<timestamp>.<raw_body>\"`\n- `X-Unigox-Timestamp: <unix_seconds>`\n\nVerification pseudocode:\n```\nexpected = HMAC-SHA256(webhook_secret, \"<timestamp>.<raw_body>\")\nvalid    = constant_time_compare(expected, signature_from_header)\n```\n\n**Delivery guarantees**\n\n- At-least-once delivery. Your endpoint may receive the same event more than once.\n- De-duplicate by `event_id`.\n- Respond with any 2xx status within 10 seconds to acknowledge receipt.\n\n**Retry policy**\n\nFailed deliveries (non-2xx or timeout) are retried with exponential backoff:\n1 min → 5 min → 15 min → 1 h → 6 h → 12 h → 24 h (max 10 attempts over ~3.5 days).\n\n**Partner-facing statuses** (`order.status.changed`)\n\n| Status | Description |\n|---|---|\n| `created` | Order created |\n| `awaiting_liquidity_provider` | Waiting for vendor match |\n| `awaiting_crypto_transfer_authorization` | Partner must authorize crypto transfer |\n| `crypto_received` | Crypto received in escrow |\n| `fiat_payment_started` | Buyer submitted fiat payment proof |\n| `fiat_payment_review_started` | Payment proof under admin review |\n| `awaiting_fiat_received_confirmation` | Proof approved, awaiting seller confirmation |\n| `completed` | Order completed, crypto released |\n| `cancelled` | Order cancelled or payment declined |\n| `failed` | Escrow error, or fiat-anchored safety bound exceeded (see below) |\n| `dispute_started` | Dispute opened |\n\n**Fiat-anchored safety bound failure**\n\nFor fiat-anchored off-ramp orders (`anchor_type: \"fiat\"`), if Switch quotes a crypto cost exceeding\n1.20× the expected amount at the time of settlement, the order moves to `failed` status asynchronously\n(not during `/initiate`). The deposit is automatically refunded from escrow back to the partner wallet.\nCreate a new quote to retry the trade flow.\n"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"RegisterWebhookRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"HTTPS URL that will receive webhook events"}}},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"WebhookConfigResponse":{"type":"object","properties":{"url":{"type":"string","nullable":true,"description":"Currently configured webhook URL (null if not set)"},"enabled":{"type":"boolean","description":"Whether webhook delivery is active"}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}},"paths":{"/api/v1/partner/webhooks":{"post":{"tags":["Webhooks"],"summary":"Register webhook URL","description":"Register or update the webhook URL for your partner account. The URL must use HTTPS.\nOnce registered, the webhook is automatically enabled and will start receiving order status events.\n","operationId":"registerWebhook","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterWebhookRequest"}}}},"responses":{"200":{"description":"Webhook registered successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookConfigResponse"}}}]}}}},"400":{"description":"Bad request — missing or invalid URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key"}}}}}}
````

## Delete webhook

> Remove the webhook URL and disable webhook delivery for your partner account.\
> No further events will be sent until a new URL is registered.<br>

````json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Webhooks","description":"Real-time notifications for order and KYC status changes.\nThe system sends a signed HTTP POST to your configured webhook URL on every relevant event.\n\n**Event model**\n\nTwo event types are delivered:\n\n| Event type | Fired when |\n|---|---|\n| `order.status.changed` | An order transitions to a new status |\n| `user.kyc.updated` | A user's KYC verification is approved or declined |\n\nAll events share the same envelope (`event_id`, `event_type`, `created_at`, `data`)\nand the same signature scheme. The structure of the `data` object differs per event type.\n\n**Payload shape — `order.status.changed`**\n\n```json\n{\n  \"event_id\": \"evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n  \"event_type\": \"order.status.changed\",\n  \"created_at\": \"2026-02-01T12:00:05Z\",\n  \"data\": {\n    \"order_id\": \"b2c3d4e5-f6a7-8901-bcde-f12345678901\",\n    \"status\": \"crypto_received\",\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"crypto_amount\": \"101.500000\",\n    \"crypto_currency\": \"USDT\",\n    \"fiat_amount\": \"152880.00\",\n    \"fiat_currency\": \"NGN\",\n    \"provider\": \"p2p\",\n    \"payment_details_id\": \"987\"\n  }\n}\n```\n\n**Payload shape — `user.kyc.updated`**\n\nFired when a user's KYC status transitions to `verified` or `rejected`.\nUse this event to unlock order creation for the user on your side.\n\n```json\n{\n  \"event_id\": \"evt_2d8418dd-e3a8-4463-a11e-6b54f354ca90\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T11:37:42Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"VERIFIED\"\n  }\n}\n```\n\nWhen KYC is declined, `rejection_reasons` is included:\n\n```json\n{\n  \"event_id\": \"evt_fc75484d-b374-4d05-b54e-820f3dd80e6d\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:00:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"REJECTED\",\n    \"rejection_reasons\": [\"Document expired\", \"Photo quality low\"]\n  }\n}\n```\n\n`kyc_status` values: `VERIFIED` | `REJECTED`\n\n**Signature verification**\n\nEvery delivery includes two headers for HMAC-SHA256 verification:\n- `X-Unigox-Signature: sha256=<hex>` — HMAC of `\"<timestamp>.<raw_body>\"`\n- `X-Unigox-Timestamp: <unix_seconds>`\n\nVerification pseudocode:\n```\nexpected = HMAC-SHA256(webhook_secret, \"<timestamp>.<raw_body>\")\nvalid    = constant_time_compare(expected, signature_from_header)\n```\n\n**Delivery guarantees**\n\n- At-least-once delivery. Your endpoint may receive the same event more than once.\n- De-duplicate by `event_id`.\n- Respond with any 2xx status within 10 seconds to acknowledge receipt.\n\n**Retry policy**\n\nFailed deliveries (non-2xx or timeout) are retried with exponential backoff:\n1 min → 5 min → 15 min → 1 h → 6 h → 12 h → 24 h (max 10 attempts over ~3.5 days).\n\n**Partner-facing statuses** (`order.status.changed`)\n\n| Status | Description |\n|---|---|\n| `created` | Order created |\n| `awaiting_liquidity_provider` | Waiting for vendor match |\n| `awaiting_crypto_transfer_authorization` | Partner must authorize crypto transfer |\n| `crypto_received` | Crypto received in escrow |\n| `fiat_payment_started` | Buyer submitted fiat payment proof |\n| `fiat_payment_review_started` | Payment proof under admin review |\n| `awaiting_fiat_received_confirmation` | Proof approved, awaiting seller confirmation |\n| `completed` | Order completed, crypto released |\n| `cancelled` | Order cancelled or payment declined |\n| `failed` | Escrow error, or fiat-anchored safety bound exceeded (see below) |\n| `dispute_started` | Dispute opened |\n\n**Fiat-anchored safety bound failure**\n\nFor fiat-anchored off-ramp orders (`anchor_type: \"fiat\"`), if Switch quotes a crypto cost exceeding\n1.20× the expected amount at the time of settlement, the order moves to `failed` status asynchronously\n(not during `/initiate`). The deposit is automatically refunded from escrow back to the partner wallet.\nCreate a new quote to retry the trade flow.\n"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}}}},"paths":{"/api/v1/partner/webhooks/{id}":{"delete":{"tags":["Webhooks"],"summary":"Delete webhook","description":"Remove the webhook URL and disable webhook delivery for your partner account.\nNo further events will be sent until a new URL is registered.\n","operationId":"deleteWebhook","parameters":[{"name":"id","in":"path","required":true,"description":"Webhook ID (any value accepted — the system uses the authenticated partner context)","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook deleted successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string"}}}}}]}}}},"401":{"description":"Unauthorized — invalid or missing API key"}}}}}}
````

## Test webhook

> Send a signed test event to your configured webhook URL. Uses the same HMAC-SHA256\
> signature scheme as production events. Use this to verify your endpoint is reachable\
> and correctly validating signatures.<br>

````json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Webhooks","description":"Real-time notifications for order and KYC status changes.\nThe system sends a signed HTTP POST to your configured webhook URL on every relevant event.\n\n**Event model**\n\nTwo event types are delivered:\n\n| Event type | Fired when |\n|---|---|\n| `order.status.changed` | An order transitions to a new status |\n| `user.kyc.updated` | A user's KYC verification is approved or declined |\n\nAll events share the same envelope (`event_id`, `event_type`, `created_at`, `data`)\nand the same signature scheme. The structure of the `data` object differs per event type.\n\n**Payload shape — `order.status.changed`**\n\n```json\n{\n  \"event_id\": \"evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n  \"event_type\": \"order.status.changed\",\n  \"created_at\": \"2026-02-01T12:00:05Z\",\n  \"data\": {\n    \"order_id\": \"b2c3d4e5-f6a7-8901-bcde-f12345678901\",\n    \"status\": \"crypto_received\",\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"crypto_amount\": \"101.500000\",\n    \"crypto_currency\": \"USDT\",\n    \"fiat_amount\": \"152880.00\",\n    \"fiat_currency\": \"NGN\",\n    \"provider\": \"p2p\",\n    \"payment_details_id\": \"987\"\n  }\n}\n```\n\n**Payload shape — `user.kyc.updated`**\n\nFired when a user's KYC status transitions to `verified` or `rejected`.\nUse this event to unlock order creation for the user on your side.\n\n```json\n{\n  \"event_id\": \"evt_2d8418dd-e3a8-4463-a11e-6b54f354ca90\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T11:37:42Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"VERIFIED\"\n  }\n}\n```\n\nWhen KYC is declined, `rejection_reasons` is included:\n\n```json\n{\n  \"event_id\": \"evt_fc75484d-b374-4d05-b54e-820f3dd80e6d\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:00:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"REJECTED\",\n    \"rejection_reasons\": [\"Document expired\", \"Photo quality low\"]\n  }\n}\n```\n\n`kyc_status` values: `VERIFIED` | `REJECTED`\n\n**Signature verification**\n\nEvery delivery includes two headers for HMAC-SHA256 verification:\n- `X-Unigox-Signature: sha256=<hex>` — HMAC of `\"<timestamp>.<raw_body>\"`\n- `X-Unigox-Timestamp: <unix_seconds>`\n\nVerification pseudocode:\n```\nexpected = HMAC-SHA256(webhook_secret, \"<timestamp>.<raw_body>\")\nvalid    = constant_time_compare(expected, signature_from_header)\n```\n\n**Delivery guarantees**\n\n- At-least-once delivery. Your endpoint may receive the same event more than once.\n- De-duplicate by `event_id`.\n- Respond with any 2xx status within 10 seconds to acknowledge receipt.\n\n**Retry policy**\n\nFailed deliveries (non-2xx or timeout) are retried with exponential backoff:\n1 min → 5 min → 15 min → 1 h → 6 h → 12 h → 24 h (max 10 attempts over ~3.5 days).\n\n**Partner-facing statuses** (`order.status.changed`)\n\n| Status | Description |\n|---|---|\n| `created` | Order created |\n| `awaiting_liquidity_provider` | Waiting for vendor match |\n| `awaiting_crypto_transfer_authorization` | Partner must authorize crypto transfer |\n| `crypto_received` | Crypto received in escrow |\n| `fiat_payment_started` | Buyer submitted fiat payment proof |\n| `fiat_payment_review_started` | Payment proof under admin review |\n| `awaiting_fiat_received_confirmation` | Proof approved, awaiting seller confirmation |\n| `completed` | Order completed, crypto released |\n| `cancelled` | Order cancelled or payment declined |\n| `failed` | Escrow error, or fiat-anchored safety bound exceeded (see below) |\n| `dispute_started` | Dispute opened |\n\n**Fiat-anchored safety bound failure**\n\nFor fiat-anchored off-ramp orders (`anchor_type: \"fiat\"`), if Switch quotes a crypto cost exceeding\n1.20× the expected amount at the time of settlement, the order moves to `failed` status asynchronously\n(not during `/initiate`). The deposit is automatically refunded from escrow back to the partner wallet.\nCreate a new quote to retry the trade flow.\n"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"WebhookTestPingResponse":{"type":"object","properties":{"delivered":{"type":"boolean","description":"Whether the test event was delivered successfully"},"http_status":{"type":"integer","description":"HTTP status code returned by the webhook endpoint"}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}},"paths":{"/api/v1/partner/webhooks/test":{"post":{"tags":["Webhooks"],"summary":"Test webhook","description":"Send a signed test event to your configured webhook URL. Uses the same HMAC-SHA256\nsignature scheme as production events. Use this to verify your endpoint is reachable\nand correctly validating signatures.\n","operationId":"testPingWebhook","responses":{"200":{"description":"Test event delivered successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookTestPingResponse"}}}]}}}},"400":{"description":"Webhook URL not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key"},"502":{"description":"Webhook delivery failed — target endpoint returned non-2xx or timed out","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
````


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developers.unigox.com/api-reference/webhooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
