Skip to content
Last updated

Banxa sends webhook notifications to your webhook endpoint URL whenever an order's status changes or a user's KYC/Account state updates. This eliminates the need to poll our APIs and allows you to track the complete user journey in real-time.


Setup

Configure your webhook endpoint URL in the Partner Dashboard. You can set separate URLs for sandbox and production.

The webhook URL must:

  • Be publicly accessible over HTTPS.
  • Return a 200 HTTP response to acknowledge receipt.

Available Webhooks

1. Order Webhooks

Order webhooks are triggered on all order status transitions. We will send an HTTP POST request with a JSON body.

Payload Example:

{
  "order_id": "d9efc5d228cb7edfc4b6bb82f7b39f94",
  "status": "complete",
  "status_date": "2026-01-1604:04:21",
  "created_at": "2026-01-1604:04:20",
  "updated_at": "2026-01-1604:04:20",
  "external_id": null,
  "order_type": "BUY",
  "crypto_coin": "USDT",
  "crypto_blockchain": "ETH",
  "crypto_amount": "67.1000000000000000",
  "fiat_currency": "AUD",
  "fiat_amount": "100",
  "asset_price": "1.490312965722801",
  "payment_method": "payid-bank-transfer",
  "processing_fee": "0",
  "network_fee": "0",
  "usd_exchange_rate": "1.4923330",
  "transaction_hash": "0",
  "metadata": []
}
FieldDescription
order_idThe unique Banxa identifier for the order.
statusThe new order status. See Order Statuses for all possible values.
status_dateISO 8601 timestamp of the most recent status transition.
created_atISO 8601 timestamp of when the order was initially created.
updated_atISO 8601 timestamp of the last time the order record was modified.
external_idThe unique ID provided by the partner during order creation (if applicable).
order_typeThe transaction direction: BUY (Fiat to Crypto) or SELL (Crypto to Fiat).
crypto_coinThe ticker symbol of the cryptocurrency (e.g., USDT, BTC).
crypto_blockchainThe specific network/blockchain used for the transaction (e.g., ETH, SOL).
crypto_amountThe total amount of cryptocurrency involved in the transaction.
fiat_currencyThe 3-letter ISO code of the fiat currency used (e.g., AUD, CAD).
fiat_amountThe total fiat amount of the transaction.
asset_priceThe price of one unit of the cryptocurrency in the source fiat currency.
payment_methodThe specific payment rail used for the transaction (e.g., payid-bank-transfer).
processing_feeThe service/gateway fee charged by Banxa in the source fiat currency.
network_feeThe blockchain network (gas) fee in the source fiat currency.
usd_exchange_rateThe exchange rate used to convert 1 unit of source fiat to USD at the time of order creation.
transaction_hashThe on-chain identifier (TXID) for the crypto transfer, if available.
metadataA collection of custom key-value pairs passed by the partner for tracking purposes.

After receiving a webhook, use the order_id to fetch full order details from the order lookup endpoint if you need additional data.


2. Identity & KYC Webhooks

2.1 KYC Webhooks

KYC webhooks are triggered whenever a user's identity verification state changes.

Please reach out to your Banxa contact if you would like to have this webhook enabled.

Payload Example:

{
  "external_customer_id": "demomerchant-61466523855",
  "account": {
    "exists": true,
    "blocked": false,
    "createdAt": "2026-03-30T05:08:11Z"
  },
  "kyc": {
    "status": "UNDER_REVIEW"
  }
}
FieldDescription
external_customer_idYour system's unique identifier for the user.
account.existsBoolean indicating if the user profile is successfully created in our system.
account.blockedBoolean indicating if the user has been banned, suspended, or hit a compliance block.
account.createdAtISO 8601 timestamp of account creation.
kyc.statusThe verification outcome of the customer's submitted identity documents (selfie + document). See status table below.

kyc.status values:

StatusDescription
PENDINGNo identity documents have been submitted yet.
UNDER_REVIEWDocuments are being reviewed.
ACTION_REQUIREDAdditional action is required from the customer.
VERIFIEDDocument and liveness verification passed.
REJECTEDVerification was unsuccessful.

Important: kyc.status reflects document and liveness verification only — it does not account for supplementary fields (e.g. purpose of transaction, occupation) or overall transaction eligibility. A VERIFIED status means the identity documents passed review; it does not mean the customer is eligible to transact. Use the order creation flow to determine whether a customer can proceed.


2.2 Enhanced Due Diligence (EDD) Webhook

This identity-level webhook is triggered when an order requires manual intervention or additional documentation (Enhanced Due Diligence). It allows you to monitor when a user has been flagged for specific verification steps beyond standard KYC.

Please reach out to your Banxa contact if you would like to have this webhook enabled.

Payload Example::

{
  "identity_reference": "demomerchant-61466233701",
  "status": "extraVerification",
  "status_date": "2026-02-13 04:39:38",
  "internal_reason": "Customer requires extra verification",
  "external_reason": "Your order is pending additional verification. We'll notify you once it's complete"
}
FieldDescription
identity_referenceThe unique identifier for the user (matches external_customer_id or account_reference).
statusThe verification status (typically extraVerification).
status_dateISO 8601 timestamp of when the additional verification was triggered.
internal_reasonThe internal system categorization for the verification request.
external_reasonThe user-facing message explaining why the transaction is pending.

Triggers: This webhook is sent when any of the following identity-level exceptions are required:

  • Enhanced Due Diligence (EDD): High-level manual review.
  • Verification Phone Call (VPC): Requirement for a manual call with the user.
  • Questionnaire: Scam check or suitability assessment.
  • Proof of Address (POA): Request for residency documentation.
  • Source of Funds (SOF): Request for documentation regarding the origin of funds.

2.3 Account Blocked Webhook

This identity-level webhook is triggered when a customer has been restricted from creating or completing orders due to compliance or risk policies. It provides the necessary context to inform your internal teams or the end user.

Payload Example:

{
  "identity_reference": "partner-customer-123",
  "status": "cancelled",
  "status_date": "2026-03-05 19:53:08",
  "internal_reason": "Account is blocked.",
  "external_reason": "Your account has been restricted from further transactions."
}
FieldDescription
identity_referenceA unique customer identifier provided by the partner
statusThe specific account status. Enum: cancelled.
status_dateISO 8601 timestamp of the status update.
internal_reasonInternal system message detailing the specific reason for the block.
external_reasonThe designated message to be displayed to the end customer via your UI.

Retry behaviour

The webhook retry mechanism ensures reliable delivery of event notifications. If your endpoint does not respond with 200 OK, Banxa will automatically retry delivery. The payload is unchanged across all retry attempts.

Retries follow a Fibonacci sequence: 1s, 2s, 3s, 5s, 8s, 13s, and so on. This continues for a maximum of 2 hours, with up to 18 retries over that period.

Respond with 200 OK immediately and process the event asynchronously to avoid unnecessary retries.

We recommend implementing idempotent webhook handling. In rare cases your endpoint may receive the same event more than once. Using order_id and status as a deduplication key will prevent duplicate processing.


Securing Webhooks

Banxa signs every webhook it sends using HMAC-SHA256. You verify this signature to confirm the request genuinely came from Banxa.

Webhook verification is the reverse of request signing. When you sign outbound API requests, you use a Banxa API path in the canonical string. When you verify an incoming webhook, you use the URI path of your own webhook endpoint — not a Banxa API path. Everything else is the same algorithm.

Each webhook includes an Authorization header:

Authorization: Bearer {API_KEY}:{SIGNATURE}:{NONCE}

Banxa constructs the signature from:

POST\nYOUR_WEBHOOK_PATH\nNONCE\nPAYLOAD

Where YOUR_WEBHOOK_PATH is the path of the endpoint Banxa is calling — for example, /webhooks/banxa — not any Banxa API path.

Verification flow

  1. Extract the Authorization header from the incoming request
  2. Strip the Bearer prefix and split on : to get receivedKey, receivedSignature, nonce
  3. Recompute the expected signature using the same algorithm, your secret, and YOUR_WEBHOOK_PATH
  4. Use a timing-safe comparison to check that receivedSignature matches — never use ==

Code examples

import hmac

MY_WEBHOOK_PATH = '/webhooks/banxa'
KEY = '[YOUR_API_KEY]'
SECRET = '[YOUR_API_SECRET]'

def verify_webhook(auth_header, request_body):
    _, token = auth_header.split('Bearer ', 1)
    received_key, received_signature, nonce = token.split(':')

    data = f"POST\n{MY_WEBHOOK_PATH}\n{nonce}\n{request_body}"
    expected = hmac.new(SECRET.encode('utf-8'), data.encode('utf-8'), 'sha256').hexdigest()

    return hmac.compare_digest(received_signature, expected)