UDA webhook events
Every UDA deposit produces three webhook deliveries — one for the source-chain confirmation, one when routing starts, and one when the destination transaction lands.
| Order | Event | What it means |
|---|---|---|
| 1 | deposit.confirmed | Source-chain deposit confirmed at the UDA address |
| 2 | uda.settlement.created | Routing Engine accepted the deposit and is executing the route |
| 3 | uda.settlement.completed | Destination transaction landed in the destination token |
All three share the
standard Stridge webhook envelope — id, type,
time, version, signed with HMAC SHA-256. The payloads below are
the inner payload object.
If a settlement fails permanently, the third event is
uda.settlement.failed instead of .completed and includes an
error field. Everything else is identical.
Subscribing
Register an endpoint in the dashboard and opt into the three event types. Stridge supports multiple subscribers — one endpoint per environment is the recommended pattern.
Always verify the webhook-signature before trusting the body. See
Verify signature for
example code in Node.js, Go, and Python.
deposit.confirmed
Fires once the source-chain deposit transaction has reached the
required number of confirmations. to_wallet_category: "uda" flags
the deposit as routed through a UDA — regular wallet deposits carry
the same event type but a different category.
{
"id": "bcc2b0c1-f75c-43b2-b1bc-c44437d775f4",
"time": "2026-04-24T06:55:59Z",
"asset": "BNB",
"symbol": "BNB",
"owner": "31d32b68-e455-42b7-9d39-d2904adbe1b8",
"tx_id": "0xb7b493e68121ce6c7c39b6e0dce56fd45899277a8a85e5c524a718a00837355c",
"balance": {
"raw": "5000000000000000",
"usd": "3.17565",
"amount": "0.005"
},
"decimal": 18,
"network_id": "9006",
"network_symbol": "bsc",
"block_number": "94359674",
"from_address": "0x5a9b7c3e2f8d4a1b6c0e9f7a3b8c2d5e1f4a7b9c",
"to_address": "0x3b7e9d1a5c2f4b8e6d0c9f3a2b1e4d5c7a9b8e0f",
"contract_address": "",
"is_token_transfer": false,
"scanner_url": "/tx/0xb7b493e68121ce6c7c39b6e0dce56fd45899277a8a85e5c524a718a00837355c",
"to_wallet_category": "uda",
"from_wallet_category": "external"
}| Field | Type | Notes |
|---|---|---|
id | string | Deposit id — same as deposit_id on the settlement events |
owner | string | UDA id for UDA deposits (not the tenant-level owner you sent on create) |
asset / symbol | string | The deposited asset symbol |
tx_id | string | Source-chain transaction hash |
balance.raw | string | Amount in the asset's smallest unit (stringified integer) |
balance.amount | string | Human-readable amount (already divided by decimal) |
balance.usd | string | Indicative USD value at detection |
network_id | string | Stridge internal network id |
network_symbol | string | Network slug (ethereum, bsc, base, arbitrum, …) |
is_token_transfer | boolean | true for ERC-20 transfers, false for native coin |
contract_address | string | ERC-20 token contract (empty when is_token_transfer is false) |
to_address | string | UDA deposit address that received the funds |
to_wallet_category | enum | "uda" for UDA deposits — use this to filter out regular wallet deposits |
from_wallet_category | enum | "external" for third-party senders |
scanner_url | string | Relative path on the chain's block explorer |
The owner field on deposit.confirmed is the UDA id, not the
owner string you passed to POST /v1/uda. Use it to look up the UDA
record or to join with uda.settlement.* events via uda_address_id.
uda.settlement.created
Fires immediately after the Routing Engine accepts the deposit and
opens a settlement. At this point the route has been picked but not
yet executed, so destination_amount and fee_amount are zero.
{
"id": "bcc2b0c1-f75c-43b2-b1bc-c44437d775f4",
"state": "created",
"scenario": "cross_chain_diff_token",
"deposit_id": "bcc2b0c1-f75c-43b2-b1bc-c44437d775f4",
"deposit_tx_id": "0xb7b493e68121ce6c7c39b6e0dce56fd45899277a8a85e5c524a718a00837355c",
"uda_address_id": "31d32b68-e455-42b7-9d39-d2904adbe1b8",
"source_network_id": "9006",
"source_token_address": "",
"source_amount": "5000000000000000",
"destination_network_id": "9001",
"destination_token_address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
"destination_address": "0x8f4a2c9e1b3d7f5a6c8e0d9b2a4c6e8f0a2c4d6e",
"destination_amount": "0",
"fee_amount": "0",
"created_at": "2026-04-24T06:56:06Z",
"updated_at": "2026-04-24T06:56:06Z"
}| Field | Type | Notes |
|---|---|---|
id / deposit_id | string | Settlement id and originating deposit id (equal on first event) |
state | enum | created |
scenario | enum | same_chain_same_token, same_chain_diff_token, cross_chain_same_token, cross_chain_diff_token |
uda_address_id | string | UDA record the deposit landed on |
source_* | Network, token, amount on the source side | |
destination_* | Where the funds will be delivered (amount is 0 until completed) | |
fee_amount | string | Routing fee in destination-token smallest units (populated on completed) |
source_token_address | string | Empty string for native coins |
uda.settlement.completed
Fires once the destination transaction is on-chain. state is
completed, destination_amount and fee_amount are populated, and
source_routing_tx_id gives you the outbound tx on the destination
chain.
{
"id": "bcc2b0c1-f75c-43b2-b1bc-c44437d775f4",
"state": "completed",
"provider": "lifi",
"scenario": "cross_chain_diff_token",
"deposit_id": "bcc2b0c1-f75c-43b2-b1bc-c44437d775f4",
"deposit_tx_id": "0xb7b493e68121ce6c7c39b6e0dce56fd45899277a8a85e5c524a718a00837355c",
"uda_address_id": "31d32b68-e455-42b7-9d39-d2904adbe1b8",
"source_network_id": "9006",
"source_token_address": "",
"source_amount": "5000000000000000",
"source_routing_tx_id": "0x158ffc8b098ae63d697485a75666a337eaab18a2167ff417861f3209dffce29b",
"destination_network_id": "9001",
"destination_token_address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
"destination_address": "0x8f4a2c9e1b3d7f5a6c8e0d9b2a4c6e8f0a2c4d6e",
"destination_amount": "3140",
"fee_amount": "18485000000000",
"created_at": "2026-04-24T06:56:06Z",
"updated_at": "2026-04-24T06:56:17Z"
}| Field | Type | Notes |
|---|---|---|
state | enum | completed on success; failed with an error field on terminal failure |
provider | string | Bridge/swap provider used (lifi, relay, …) |
destination_amount | string | Delivered amount in the destination token's smallest unit |
fee_amount | string | Total fee (gas + protocol) in the destination token's smallest unit |
source_routing_tx_id | string | Destination-chain transaction that delivered the funds |
Full example timeline
t = 0.0s User sends 0.005 BNB on BSC → UDA address
t = 7.0s Observer detects tx → deposit.confirmed
t = 7.2s UDA opens settlement, picks route via LI.FI → uda.settlement.created
t = 18.0s LI.FI swap + bridge completes on Arbitrum → uda.settlement.completed
Total wall time for a cross-chain, cross-token settlement is typically 10–30 seconds on L2 destinations, and 60–90 seconds on Ethereum mainnet.
Consumer checklist
- Verify every signature. See Verify signature.
- Deduplicate by envelope
id. Stridge retries failed deliveries up to six times; every retry reuses the sameid. - Treat
stateas the source of truth, not event ordering. Ifcreatedis delayed andcompletedlands first, truststate+updated_atrather than arrival order. - Join
deposit.confirmedand settlement events via the deposit id.deposit.confirmed.id == uda.settlement.*.deposit_id. - Mirror
uda.settlement.failed. Treat it as terminal and surface theerrorfield to support tooling; Stridge does not retry failed settlements automatically.
Next
- Settlements — state machine, failure modes,
and the
GET /v1/settlements/{id}contract. - Server setup — envelope, retries, handler contract.
- Verify signature — signing algorithm and verification snippets.