Event structure
The webhook envelope, every event kind we emit, and what rides inside data.object.
The envelope
{
"id": "wev_033y3HiVQoqY1Y4lCwUxSz",
"kind": "endorsement.approved",
"environment": "sandbox",
"sequence": 118,
"created_at": "2026-08-26T10:15:32+00:00",
"data": {
"object": {
"name": "endo_usd",
"status": "approved",
"requirements": {
"complete": ["terms_of_service", "identity", "selfie", "proof_of_address", "aml"],
"uploaded": [],
"pending": [],
"missing": [],
"issues": []
}
}
}
}
| Field | Meaning |
|---|---|
id | The event id, wev_ prefixed. Stable across retries and replays. Use it as your dedupe key |
kind | The event kind, always resource.verb. The catalog is below |
environment | sandbox or live. A cheap assertion that your consumer is wired to the right place |
sequence | A monotonically increasing per-partner counter. Order your projection by it, not by arrival |
created_at | When the event was emitted, ISO 8601 with an offset |
data.object | The resource, byte-identical to what the matching GET returns |
replayed | Present and true only on a manual replay |
data.object is the same serializer as the API. One shape on both
paths, so your models never fork, and anything you can parse from a GET you
can parse from an event.
Event kinds
Events fire on the lifecycle transitions in Statuses & Lifecycles, using the same status vocabulary and the same objects.
Onboarding
| Kind | Fires when | data.object |
|---|---|---|
tos.accepted | A hosted terms link was accepted | The terms link, carrying signed_tos_id |
endorsement.approved | The endorsement approved, and the currency's features open | The endorsement |
endorsement.request_for_information | A retryable ask. The requirements buckets say what to fix | The endorsement |
endorsement.rejected | A final adverse decision | The endorsement |
There are no verification.* events. Verification decisions are announced
as endorsement.*, because the endorsement is the only public KYC reading.
See Verification & Endorsements.
Money movement
| Kind | Fires when | data.object |
|---|---|---|
virtual_account.activated | An account is live with deposit instructions | The virtual account |
deposit.received | Fiat arrived on a virtual account | The deposit |
deposit.returned | A deposit was sent back to the sender | The deposit |
deposit.on_hold | A deposit is held pending compliance review | The deposit |
conversion.created | A conversion opened for a deposit | The conversion |
conversion.completed | FX and fees settled, delivery starting | The conversion, with final delivered_amount |
payout.completed | Stablecoin delivered on chain | The payout, with tx_hash |
Wallets
| Kind | Fires when | data.object |
|---|---|---|
wallet.created | A wallet was created | The wallet |
wallet.deposit_received | Funds arrived at a wallet address, including sends you did not initiate | The wallet activity row |
wallet.withdrawal_created | Funds left a wallet | The wallet activity row |
wallet.deposit_returned | A wallet deposit was returned | The wallet activity row |
wallet.operation_confirmed | An authorized send landed | The operation, with tx_hash |
wallet.operation_failed | An authorized send did not land. Nothing moved | The operation |
wallet.deposit_received is the one to wire up first if you use wallets.
Because a wallet is funded by a plain on-chain send, it is how you learn a
customer was paid. There is no API call to observe instead.
Subscribing to a subset
Pass event_kinds when you register the endpoint:
curl -s $BASE/api/webhooks \
-H "Api-Key: $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"url": "https://yourapp.example/rasto/kyc",
"event_kinds": ["endorsement.approved",
"endorsement.request_for_information",
"endorsement.rejected"]}'
Omitting event_kinds subscribes to everything, and the registered
endpoint reads back ["*"].
New event kinds are additive and ship without a version bump, so ignore kinds you do not recognize rather than failing on them.
Reading the history
GET /api/webhook_events returns what we emitted, whether or not it was
delivered:
{
"data": [
{
"id": "wev_033y3HiVQoqY1Y4lCwUxSz",
"object": "webhook_event",
"kind": "endorsement.approved",
"sequence": 118,
"data": {"object": {}},
"published_at": "2026-08-26T10:15:33+00:00",
"created_at": "2026-08-26T10:15:32+00:00"
}
],
"has_more": true,
"next_cursor": "..."
}
A published_at of null means the event has not gone out yet. Page
through anything newer than your last processed sequence to backfill
after downtime.