Exchange Webhooks

Real-time notifications for orders, withdrawals, deposits, and beneficiaries

Overview

Exchange emits webhooks whenever one of your resources changes state, so you can react to events as they happen instead of polling the API. Each event is delivered as an HTTP POST to the endpoint you configure, with the body in a consistent envelope:

1{
2 "eventType": "order.filled",
3 "timestamp": "2026-06-09T08:15:30Z",
4 "payload": { }
5}
FieldTypeDescription
eventTypestringThe event that fired, e.g. order.filled.
timestampstring (date-time)When the event was generated.
payloadobjectThe resource in its state at the time of the event. The shape depends on the resource (see Payloads below).

Webhooks are delivered via Svix, and the signing, retry, and recovery mechanics are identical to those described on the Webhooks page. This guide covers the Exchange-specific event types and payloads; see Securing and delivery for the shared mechanics.

Event Types

Exchange emits events across four resources. The payload for an event matches the resource named in its eventType.

Order events — payload is an Order:

Event TypeDescription
order.filledThe order reached filled — the trade is complete and balances are updated.
order.cancelledThe order was cancelled before trading and balances were reverted.

Withdrawal events — payload is a Withdrawal:

Event TypeDescription
withdrawal.pendingA withdrawal was created and is awaiting dispatch.
withdrawal.processingThe withdrawal is being dispatched to the payment rail.
withdrawal.completedThe withdrawal completed and the funds were sent.
withdrawal.failedThe withdrawal failed before dispatch; the reservation was released.
withdrawal.cancelledThe withdrawal was cancelled while pending.
withdrawal.returnedThe withdrawal was returned by the receiving bank; the balance was restored.

Deposit events — payload is a Deposit:

Event TypeDescription
deposit.pendingA deposit was detected and is awaiting crediting.
deposit.creditedA deposit was credited and the funds are available.

Beneficiary events — payload is a Beneficiary:

Event TypeDescription
beneficiary.pendingA beneficiary was created and is awaiting activation.
beneficiary.activeA beneficiary reached active and can be used in withdrawals.
beneficiary.rejectedA beneficiary was rejected and cannot be used in withdrawals.
beneficiary.disabledA beneficiary was disabled and can no longer be used in withdrawals.

Payloads

Each payload is the same object you receive from the corresponding Exchange API endpoint, so you can reuse the same parser for both. The full field-level schemas live in the Exchange Webhooks API Reference. The key fields and status values for each resource are summarised below.

Order

FieldTypeDescription
orderIdstringUnique order identifier.
quoteIdstringThe originating quote.
statusstringOne of pending, filling, filled, cancelled, failed.
sellCurrency / buyCurrencystringCurrencies traded.
sellAmount / buyAmount / ratestringLocked at quote acceptance.
createdAt / updatedAtstring (date-time)Creation and last-update timestamps.

Withdrawal

FieldTypeDescription
withdrawalIdstringUnique withdrawal identifier.
statusstringOne of pending, processing, completed, cancelled, failed, returned.
statusReasonstringMachine-readable context (e.g. compliance_review, returned_by_bank). Present when available.
beneficiaryIdstringThe destination beneficiary.
currency / amountstringWithdrawal currency and amount.
referencestringReference shown on the rail.
clientReferencestringYour own reference, if supplied.
paymentTypestringRail inherited from the beneficiary (e.g. ach, swift, crypto).
createdAt / updatedAtstring (date-time)Creation and last-update timestamps.

Deposit

FieldTypeDescription
depositIdstringUnique deposit identifier.
statusstringOne of pending, credited, rejected.
statusReasonstringMachine-readable context (e.g. compliance_review). Present when available.
currency / amountstringDeposit currency and amount received.
referencestringThe reference that attributed the incoming wire.
receivedAtstring (date-time)When the incoming wire was detected.
creditedAtstring (date-time)When funds were credited. Absent while pending.

Beneficiary

FieldTypeDescription
beneficiaryIdstringUnique beneficiary identifier.
statusstringOne of pending, active, rejected, disabled.
statusReasonstringMachine-readable context (e.g. compliance_review). Present when available.
currencystringOne currency per beneficiary.
name / entityTypestringLegal name and business or individual.
accountDetails / routingDetailsarrayAccount identifiers and rail routing.
createdAt / activatedAtstring (date-time)Creation and activation timestamps (activatedAt absent until active).

Example Webhook Payloads

A filled order:

1{
2 "eventType": "order.filled",
3 "timestamp": "2026-06-09T08:15:30Z",
4 "payload": {
5 "orderId": "ord_6ba7b810-9dad-11d1-80b4-00c04fd430c8",
6 "quoteId": "qut_550e8400-e29b-41d4-a716-446655440000",
7 "status": "filled",
8 "sellCurrency": "USD",
9 "buyCurrency": "NGN",
10 "sellAmount": "10000.00",
11 "buyAmount": "16750000.00",
12 "rate": "1675.00",
13 "createdAt": "2026-06-09T08:15:25Z",
14 "updatedAt": "2026-06-09T08:15:30Z"
15 }
16}

A completed withdrawal:

1{
2 "eventType": "withdrawal.completed",
3 "timestamp": "2026-04-21T14:30:00Z",
4 "payload": {
5 "withdrawalId": "wth_6ba7b810-9dad-11d1-80b4-00c04fd430c8",
6 "status": "completed",
7 "beneficiaryId": "ben_f47ac10b-58cc-4372-a567-0e02b2c3d479",
8 "currency": "NGN",
9 "amount": "16750000.00",
10 "reference": "Invoice 8821 Acme",
11 "clientReference": "wd-2026-04-21-0001",
12 "paymentType": "local",
13 "createdAt": "2026-04-21T14:20:00.000Z",
14 "updatedAt": "2026-04-21T14:30:00.000Z"
15 }
16}

A credited deposit:

1{
2 "eventType": "deposit.credited",
3 "timestamp": "2026-04-21T14:25:00Z",
4 "payload": {
5 "depositId": "dep_550e8400-e29b-41d4-a716-446655440000",
6 "status": "credited",
7 "currency": "USD",
8 "amount": "10000.00",
9 "reference": "CC-ACME-USD-7Q3K",
10 "receivedAt": "2026-04-21T14:20:00.000Z",
11 "creditedAt": "2026-04-21T14:25:00.000Z"
12 }
13}

An activated beneficiary:

1{
2 "eventType": "beneficiary.active",
3 "timestamp": "2026-04-21T09:00:00Z",
4 "payload": {
5 "beneficiaryId": "ben_f47ac10b-58cc-4372-a567-0e02b2c3d479",
6 "status": "active",
7 "currency": "NGN",
8 "name": "Acme Industries Ltd",
9 "entityType": "business",
10 "accountDetails": [
11 { "type": "account_number", "value": "0123456789" }
12 ],
13 "routingDetails": [
14 { "rail": "fiat", "paymentType": "local", "routingNumberType": "other", "routingNumber": "058" }
15 ],
16 "createdAt": "2026-04-21T08:55:00Z",
17 "activatedAt": "2026-04-21T09:00:00Z"
18 }
19}

Handling events

Switch on eventType to route each event to the right handler. The payload already matches the resource you would fetch from the API, so you can reuse your existing models:

1const event = JSON.parse(payload);
2
3switch (event.eventType) {
4 case "order.filled":
5 await handleOrderFilled(event.payload);
6 break;
7 case "withdrawal.completed":
8 await handleWithdrawalCompleted(event.payload);
9 break;
10 case "withdrawal.returned":
11 await handleWithdrawalReturned(event.payload);
12 break;
13 case "deposit.credited":
14 await handleDepositCredited(event.payload);
15 break;
16 case "beneficiary.active":
17 await handleBeneficiaryActive(event.payload);
18 break;
19 // Handle other event types
20}

Securing and delivery

Exchange webhooks use the same delivery infrastructure as the rest of CrissCross, so the following all work exactly as documented on the Webhooks page:

  • Subscribing — create and configure endpoints from the webhooks section of your CrissCross dashboard, selecting the Exchange events you want to receive.
  • Signature verification — every delivery is signed with Svix svix-id, svix-timestamp, and svix-signature headers. Verify them before processing. See Securing Webhooks.
  • Idempotency — deduplicate on the Svix message ID, since retries and manual replays can deliver the same event more than once. See Best Practices.
  • Retries — failed deliveries are retried on an exponential backoff schedule. See Webhook Retries.
  • Recovery — resend, replay, and recover past messages from the dashboard. See Resending and Replaying Webhooks.

Acknowledge every delivery by returning any 2xx status. Anything else (including 3xx redirects and timeouts) is treated as a failure and retried.