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
Three event types are delivered:
order.status.changed
An order transitions to a new status
order.refund.required
An order's crypto is stuck in escrow and only your signature can return it
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
{
"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_id": "550e8400-e29b-41d4-a716-446655440000",
"crypto_amount": "101.5",
"crypto_currency": "USDT",
"partner_fee": "1",
"partner_fee_pct": 1,
"fiat_amount": "152880.00",
"fiat_currency": "NGN",
"provider": "p2p",
"payment_details_id": "987"
}
}Payload shape — order.refund.required
Fired when an off-ramp order has stopped without a payout and its crypto is still in escrow. This has its own event type because there is no status change to carry it: the order reads cancelled from the moment it stopped, exactly like an order whose refund was already paid out, so a listener filtering on status transitions would never learn that anything is owed. It is sent at most once per order, and the same information is on GET /orders/{order_id} for as long as it stays true.
The data object is the order.status.changed payload plus action_required.
Act on it with the refund pair: GET /orders/{order_id}/refund-authorization-parameters for the transaction to sign, then POST /orders/{order_id}/authorize-refund with the signature. Nobody else can produce that signature — the escrow needs two of its three owners, we hold one key and you hold the other.
Payload shape — user.kyc.updated
Fired when a user's KYC status changes — to verified, rejected, or to a manual compliance review hold (UNDER_REVIEW). Use this event to unlock order creation for the user on your side (only on VERIFIED; UNDER_REVIEW is not cleared).
When KYC is declined, rejection_reasons is included:
When a customer is placed on a manual compliance review hold, kyc_status is UNDER_REVIEW:
kyc_status values: VERIFIED | REJECTED | UNDER_REVIEW
UNDER_REVIEW is a manual compliance review: the customer is NOT cleared and can take up to 24 hours (unlike an automated check of a couple of minutes). Do not unlock order creation until VERIFIED.
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:
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)
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 the crypto cost we quote exceeds 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.
Settlement notifications (recipient received the fiat)
Some liquidity providers automatically confirm when the fiat payout reaches the recipient's bank account or mobile wallet. When the provider matched to an order supports this, the order moves to completed on its own and you receive an order.status.changed webhook with status=completed — that webhook is your signal that the recipient has actually received the money.
Whether the matched provider sends this is reported per order by the has_fiat_settlement_notification field on the quote and order responses:
true— the matched provider sends an automatic settlement confirmation. You will receive thecompletedwebhook once fiat is delivered; no manual action is needed.false— no automatic confirmation for this order. Confirm delivery yourself by callingconfirm-fiat-received(or rely on your own banking/payment systems).
To match only providers that send settlement notifications, pass has_fiat_settlement_notification: true in the quote request — providers that can't offer it are then excluded from matching.
Availability is provider- and corridor-dependent: not every currency has a provider that sends settlement notifications, so always read the flag on each quote rather than assuming it. Manual confirm-fiat-received and the automatic confirmation are mutually idempotent — whichever happens first wins and the other is a safe no-op.
Returns the current webhook configuration for your partner account, including the URL and whether the webhook is enabled.
Partner API key for authentication. Required for all partner account endpoints.
Webhook configuration retrieved
Indicates if the request was successful
trueUnauthorized — invalid or missing API key
GET /api/v1/partner/webhooks HTTP/1.1
Host: api-staging.unigox.com
X-API-Key: YOUR_API_KEY
Accept: */*
{
"success": true,
"data": {
"url": "https://partner.example.com/webhooks/unigox",
"enabled": true
}
}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.
Partner API key for authentication. Required for all partner account endpoints.
HTTPS URL that will receive webhook events
https://partner.example.com/webhooks/unigoxWebhook registered successfully
Indicates if the request was successful
trueBad request — missing or invalid URL
Unauthorized — invalid or missing API key
POST /api/v1/partner/webhooks HTTP/1.1
Host: api-staging.unigox.com
X-API-Key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 53
{
"url": "https://partner.example.com/webhooks/unigox"
}{
"success": true,
"data": {
"url": "https://partner.example.com/webhooks/unigox",
"enabled": true
}
}Remove the webhook URL and disable webhook delivery for your partner account. No further events will be sent until a new URL is registered.
Partner API key for authentication. Required for all partner account endpoints.
Webhook ID (any value accepted — the system uses the authenticated partner context)
1Webhook deleted successfully
Indicates if the request was successful
trueUnauthorized — invalid or missing API key
DELETE /api/v1/partner/webhooks/{id} HTTP/1.1
Host: api-staging.unigox.com
X-API-Key: YOUR_API_KEY
Accept: */*
{
"success": true,
"data": {
"message": "webhook deleted"
}
}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.
Partner API key for authentication. Required for all partner account endpoints.
Test event delivered successfully
Indicates if the request was successful
trueWebhook URL not configured
Unauthorized — invalid or missing API key
Webhook delivery failed — target endpoint returned non-2xx or timed out
POST /api/v1/partner/webhooks/test HTTP/1.1
Host: api-staging.unigox.com
X-API-Key: YOUR_API_KEY
Accept: */*
{
"success": true,
"data": {
"delivered": true,
"http_status": 200
}
}Last updated
