Webhooks
Event Types

Event Types

⚠️

All webhook bodies are application/x-www-form-urlencoded, so every value is a string. A field whose value is null arrives as the literal 4-character string "null" (not JSON null, not an absent key). Before parsing numbers, treat "null" and "" as absent — e.g. Number("null") is NaN, and if (contractAddress) is truthy for the string "null". Fields typed string | null below follow this rule.

1. DEPOSIT

Sent when a user's deposit is confirmed on-chain and funds have been swept to the bankroll.

Delivery: Asynchronous, via a durable outbox — retried up to 5× with backoff, then dead-lettered (see Retries). Return { "error": 0 } to acknowledge; any non-zero error (or a non-2xx / non-JSON response) is treated as a failed delivery and retried — so acknowledge with 0 even for duplicates or events you choose to ignore.

Payload fields:

FieldTypeDescription
eventstringDEPOSIT
depositIdnumberInternal deposit ID
idempotencyKeystringUnique deduplication key for this deposit
externalUserIdstringThe user identifier you provided when creating the deposit ticket
amountstringNet raw amount that reached the bankroll (gross − fee), token's smallest unit
amountInDollarstring | nullNet USD value — credit this to the user
amountInLocalCurrencystring | nullNet value in your platform's local fiat (e.g. TRY). Equals amountInDollar when your platform currency is USD
localCurrencyCodestringYour platform's fiat code used for the conversion ("USD" when none is configured)
currentRatestringUSD → localCurrencyCode rate used at settlement ("1" for USD)
pairstringConversion pair, e.g. "USD/TRY"
grossAmountstringRaw amount the user actually sent on-chain
feeAmountstringRaw fee deducted on settlement ("0" when no fee)
feeInDollarstring | nullUSD value of the fee ("0.000000" when no fee)
typestringERC20 or NATIVE
contractAddressstring | nullToken contract address (null for native transfers)
transactionHashstring | nullInbound (deposit) transaction hash
chainIdnumberChain ID where the deposit occurred
confirmedAtstringISO 8601 timestamp

Amounts: amount / amountInDollar are the net credited to you (fee already deducted). Reconciles as amount + feeAmount = grossAmount. With no fee configured, feeAmount = "0" and grossAmount = amount. Credit amountInDollar, not the raw amount.

Local currency: amountInLocalCurrency / currentRate are a snapshot taken at settlement in your platform's configured fiat, so you can book the deposit in your own currency without a second lookup. The rate can move afterwards — these values are the ones in effect when the deposit confirmed. When your platform currency is USD, localCurrencyCode = "USD", currentRate = "1", and amountInLocalCurrency = amountInDollar.

Expected response:

{
  "error": 0,
  "description": "ok",
  "transactionId": "your-internal-id"
}

Idempotency: Use idempotencyKey to deduplicate. You may receive the same deposit event more than once.


2. WITHDRAW_REQUEST

Sent to request your approval of a withdrawal. Delivered one of two ways depending on your operator's approval mode (see Withdrawals):

  • Synchronous mode (default): sent before the withdrawal record exists, and the system waits for your response — return error: 0 to approve, non-zero to deny. withdrawRequestId is empty.
  • External-approval mode: sent asynchronously (retried via our outbox) after the request is created as AWAITING_APPROVAL. Your response only acknowledges receipt (error: 0); the real decision is a separate callback you make to POST /withdraw/{withdrawRequestId}/decision. Here withdrawRequestId is the real withdrawal UUID.

Both modes send the identical event: "WITHDRAW_REQUEST" payload — the only wire difference is whether withdrawRequestId is populated. In external-approval mode, always acknowledge with { "error": 0 } and drive the outcome from your callback.

Delivery: Synchronous (default mode) or asynchronous + retried (external-approval mode).

Payload fields:

FieldTypeDescription
eventstringWITHDRAW_REQUEST
withdrawRequestIdstringExternal-approval mode: the withdrawal UUID to use in your /withdraw/{id}/decision callback. Synchronous mode: empty ("")
sessionIdstringThe owning session UUID (the sid) — always present. Use it to correlate this request with the later WITHDRAW_COMPLETE, especially in synchronous mode where withdrawRequestId is empty
externalUserIdstringThe user identifier
typestringDIRECT_TRANSFER or OFF_RAMP
addressstringDestination wallet address
cryptoCurrencyCodestringe.g. USDT, ETH
fiatCurrencyCodestringe.g. USD, EUR
fiatAmountstringFiat amount of the withdrawal
cryptoAmountstring | nullCrypto amount as specified by the user (null for fiat-denominated withdrawals)
cryptoAmountSentstring | nullThe crypto amount that will actually be sent on-chain — use this to reconcile the token ledger when the user gave a fiat amount
amountInUsdstring | nullUSD value — check this against the user's balance
requestedAtstringISO 8601 timestamp

To approve - return:

{
  "error": 0,
  "description": "ok"
}

To deny - return a non-zero error code:

{
  "error": 5,
  "description": "Daily withdrawal limit exceeded"
}

Error codes:

CodeMessage Shown to User
0(approved)
1Withdrawal request declined by operator
2Insufficient balance for withdrawal
3Withdrawal temporarily unavailable
4User is not eligible for withdrawal
5Daily withdrawal limit exceeded
6Withdrawal amount too low
7Withdrawal amount too high
8Account verification required
9Withdrawal suspended for this account

Any other non-zero code will show the description field from your response as the error message.


3. WITHDRAW_COMPLETE

Sent after a withdrawal has been completed (funds paid out on-chain).

Delivery: Asynchronous, via a durable outbox — retried up to 5× with backoff, then dead-lettered (see Retries). Return { "error": 0 } to acknowledge; any non-zero error (or a non-2xx / non-JSON response) is treated as a failed delivery and retried, so acknowledge even for duplicates.

Payload fields:

FieldTypeDescription
eventstringWITHDRAW_COMPLETE
withdrawRequestIdstringThe withdrawal UUID — the same id sent on the WITHDRAW_REQUEST event (external-approval mode), so you can correlate a completion back to the request you approved
sessionIdstringThe owning session UUID (the sid). Provided for integrations that correlate by session
externalUserIdstringThe user identifier
typestringDIRECT_TRANSFER or OFF_RAMP
statusstringCOMPLETED
addressstringDestination wallet address
cryptoCurrencyCodestringe.g. USDT, ETH
fiatCurrencyCodestringe.g. USD, EUR
fiatAmountstring | nullFiat amount (may be absent for a crypto-denominated withdrawal — see the form-encoding note at the top)
cryptoAmountstring | nullCrypto amount as specified by the user
cryptoAmountSentstring | nullThe crypto amount actually sent on-chain (matches WITHDRAW_REQUEST.cryptoAmountSent) — reconcile your token ledger against this
amountInUsdstring | nullUSD value of the withdrawal
completedAtstringISO 8601 timestamp

Expected response:

{
  "error": 0,
  "description": "ok"
}