Events
The gateway emits six event types. Each is opt-in — your webhook receives only events you've subscribed to in the dashboard. There is no per-event API-key permission; subscription is managed in your dashboard's Webhooks page.
| Event | Fires when |
|---|---|
deposit.detected | A pending deposit is visible on-chain but not yet confirmed. |
deposit.confirmed | Required confirmations reached; funds credited to your internal balance. |
deposit.swept | Gateway moved the deposit from the per-deposit address into your receiving wallet. |
withdrawal.broadcasted | Gateway signed and broadcast your withdrawal transaction. |
withdrawal.confirmed | Withdrawal reached required confirmations; balance debited. |
withdrawal.failed | Withdrawal did not reach the network or was rejected. |
Envelope
Every event is wrapped in the same envelope:
{
"id": 42,
"event": "deposit.confirmed",
"created_at": "2026-05-16T22:00:00.481523Z",
"api_version": "1",
"data": { ... event-specific, see below ... }
}
| Field | Type | Notes |
|---|---|---|
id | integer | Delivery id. Stable across all retries of the same event. Receivers MUST dedupe on this. |
event | string | One of the six event codes above. Also surfaced in the X-CPG-Event header. |
created_at | ISO-8601 UTC | When the gateway scheduled this delivery (not when the on-chain event happened — data carries the on-chain timestamps). Z-suffixed; fractional seconds appear only when non-zero, as 6-digit microseconds (e.g. …00.481523Z, or …00Z when exactly zero). |
api_version | string | Currently "1". Bumped only on breaking payload changes — never silently. |
data | object | Event-specific. Shape locked per event below. |
All decimal amounts are strings to avoid float precision loss. All timestamps are ISO-8601 UTC.
deposit.detected
A new transaction toward one of your deposit addresses appeared on-chain. Has not yet reached min_confirmations.
{
"deposit_request_id": 42,
"user_identifier": null,
"address": "0xabc...def",
"currency": "USDT",
"network": "ETH",
"amount": "100.000000",
"tx_hash": "0x9a8...",
"block_number": 21345678,
"confirmations": 0,
"required_confirmations": 12,
"detected_at": "2026-05-16T22:00:00.000Z"
}
Use this to show "pending deposit" UI to your user. Don't credit anything internally yet — wait for deposit.confirmed.
deposit.confirmed
The detected transaction reached required_confirmations. Funds are now credited to your /v1/balances.
{
"deposit_request_id": 42,
"user_identifier": null,
"address": "0xabc...def",
"currency": "USDT",
"network": "ETH",
"amount": "100.000000",
"tx_hash": "0x9a8...",
"block_number": 21345678,
"confirmations": 12,
"credited_at": "2026-05-16T22:05:30.000Z"
}
This is the primary trigger for any business logic that depends on a deposit being final — credit the user's in-app balance, fulfill an order, etc.
deposit.swept
The gateway moved the deposit out of the per-deposit address into your configured receiving wallet. This is operational — no balance change for you (it was already credited at deposit.confirmed).
{
"deposit_request_id": 42,
"from_address": "0xabc...def",
"to_address": "0xreceiving...wallet",
"currency": "USDT",
"network": "ETH",
"amount": "100.000000",
"sweep_tx_hash": "0xsw...",
"swept_at": "2026-05-16T22:10:00.000Z"
}
Most integrators ignore this event. Useful if you're reconciling your receiving wallet's on-chain balance against your internal ledger.
withdrawal.broadcasted
The chain microservice accepted your withdrawal and broadcast it to the network. Awaiting confirmations.
{
"withdrawal_id": 17,
"to_address": "0xrecipient...",
"currency": "USDT",
"network": "ETH",
"amount": "50.000000",
"fee": "0.50",
"tx_hash": "0xwd...",
"broadcasted_at": "2026-05-16T22:15:00.000Z"
}
Use this to update your UI from "submitting" to "broadcasted — awaiting confirmation".
withdrawal.confirmed
The withdrawal reached min_confirmations. Final state — your balance has been debited by amount + fee.
{
"withdrawal_id": 17,
"tx_hash": "0xwd...",
"confirmations": 12,
"confirmed_at": "2026-05-16T22:20:00.000Z"
}
Compact on purpose — currency, network, amount, fee, to_address were already delivered in withdrawal.broadcasted. Correlate by withdrawal_id.
withdrawal.failed
The withdrawal couldn't be broadcast, was rejected by the chain, or was cancelled by an admin. The balance hold has been released (your available balance is restored). Final state.
{
"withdrawal_id": 17,
"tx_hash": null,
"reason": "no_hot_wallet",
"error_message": "no_hot_wallet",
"failed_at": "2026-05-16T22:16:00.000Z"
}
reason is a normalised enum:
| Value | Meaning |
|---|---|
no_hot_wallet | No hot wallet is configured for this currency's network. Enable a currency on that network first. |
amount_below_smallest_unit | The requested amount rounds to zero at the currency's on-chain precision. |
broadcast_rejected | The chain microservice could not broadcast the transaction. Raw RPC error is in error_message. |
cancelled_by_admin | An admin cancelled the withdrawal via the dashboard before it was broadcast. |
tx_hash is null on every reason above (all are pre-broadcast failures).
At-least-once delivery
Webhook delivery is at-least-once. The same event may arrive more than once:
- Retries can produce duplicates if your
2xxresponse was lost in flight. - Manual admin requeues create a fresh attempt of an already-delivered row.
Always dedupe on the envelope's id — it's the same integer across every attempt of the same delivery.