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:
| Field | Type | Description |
|---|---|---|
event | string | DEPOSIT |
depositId | number | Internal deposit ID |
idempotencyKey | string | Unique deduplication key for this deposit |
externalUserId | string | The user identifier you provided when creating the deposit ticket |
amount | string | Net raw amount that reached the bankroll (gross − fee), token's smallest unit |
amountInDollar | string | null | Net USD value — credit this to the user |
amountInLocalCurrency | string | null | Net value in your platform's local fiat (e.g. TRY). Equals amountInDollar when your platform currency is USD |
localCurrencyCode | string | Your platform's fiat code used for the conversion ("USD" when none is configured) |
currentRate | string | USD → localCurrencyCode rate used at settlement ("1" for USD) |
pair | string | Conversion pair, e.g. "USD/TRY" |
grossAmount | string | Raw amount the user actually sent on-chain |
feeAmount | string | Raw fee deducted on settlement ("0" when no fee) |
feeInDollar | string | null | USD value of the fee ("0.000000" when no fee) |
type | string | ERC20 or NATIVE |
contractAddress | string | null | Token contract address (null for native transfers) |
transactionHash | string | null | Inbound (deposit) transaction hash |
chainId | number | Chain ID where the deposit occurred |
confirmedAt | string | ISO 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: 0to approve, non-zero to deny.withdrawRequestIdis 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 toPOST /withdraw/{withdrawRequestId}/decision. HerewithdrawRequestIdis 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:
| Field | Type | Description |
|---|---|---|
event | string | WITHDRAW_REQUEST |
withdrawRequestId | string | External-approval mode: the withdrawal UUID to use in your /withdraw/{id}/decision callback. Synchronous mode: empty ("") |
sessionId | string | The 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 |
externalUserId | string | The user identifier |
type | string | DIRECT_TRANSFER or OFF_RAMP |
address | string | Destination wallet address |
cryptoCurrencyCode | string | e.g. USDT, ETH |
fiatCurrencyCode | string | e.g. USD, EUR |
fiatAmount | string | Fiat amount of the withdrawal |
cryptoAmount | string | null | Crypto amount as specified by the user (null for fiat-denominated withdrawals) |
cryptoAmountSent | string | null | The crypto amount that will actually be sent on-chain — use this to reconcile the token ledger when the user gave a fiat amount |
amountInUsd | string | null | USD value — check this against the user's balance |
requestedAt | string | ISO 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:
| Code | Message Shown to User |
|---|---|
0 | (approved) |
1 | Withdrawal request declined by operator |
2 | Insufficient balance for withdrawal |
3 | Withdrawal temporarily unavailable |
4 | User is not eligible for withdrawal |
5 | Daily withdrawal limit exceeded |
6 | Withdrawal amount too low |
7 | Withdrawal amount too high |
8 | Account verification required |
9 | Withdrawal 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:
| Field | Type | Description |
|---|---|---|
event | string | WITHDRAW_COMPLETE |
withdrawRequestId | string | The 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 |
sessionId | string | The owning session UUID (the sid). Provided for integrations that correlate by session |
externalUserId | string | The user identifier |
type | string | DIRECT_TRANSFER or OFF_RAMP |
status | string | COMPLETED |
address | string | Destination wallet address |
cryptoCurrencyCode | string | e.g. USDT, ETH |
fiatCurrencyCode | string | e.g. USD, EUR |
fiatAmount | string | null | Fiat amount (may be absent for a crypto-denominated withdrawal — see the form-encoding note at the top) |
cryptoAmount | string | null | Crypto amount as specified by the user |
cryptoAmountSent | string | null | The crypto amount actually sent on-chain (matches WITHDRAW_REQUEST.cryptoAmountSent) — reconcile your token ledger against this |
amountInUsd | string | null | USD value of the withdrawal |
completedAt | string | ISO 8601 timestamp |
Expected response:
{
"error": 0,
"description": "ok"
}