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": {
"object": "endorsement",
"customer_id": "cus_033y1FhTOmoW9W2jAuSvQx",
"name": "endo_usd",
"status": "approved",
"requirements": {
"complete": ["compliance_review", "identity", "proof_of_address", "selfie", "terms_of_service"],
"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, such as deposit.received. The catalog is below |
environment | sandbox or live. A cheap assertion that your consumer is wired to the right place |
sequence | An ever-increasing counter with gaps. Order your projection by it, not by arrival |
created_at | When the event was emitted, ISO 8601 with an offset |
data.object | The resource, built by the same serializer as the API, so a model that parses a GET response parses the event too |
replayed | Present and true only on a manual replay |
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 |
|---|---|---|
customer.created | A customer was created | The customer |
tos_acceptance.created | A hosted terms link was accepted | The tos_acceptance, carrying signed_tos_id |
endorsement.approved | The endorsement approved, and the currency's features open | The endorsement |
endorsement.request_for_information | A retryable ask, including one on an approved endorsement. 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 verification
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. Produced in sandbox today; in live our team resolves a reversed credit with you | The deposit |
deposit.on_hold | A deposit is held pending compliance review. Produced in sandbox today; in live our team contacts you | The deposit |
conversion.created | A conversion opened for a deposit | The conversion |
conversion.completed | Delivery landed on chain. Emitted with payout.completed | 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_transaction.created | Funds arrived at a wallet address, including sends you did not initiate (kind: "deposit"), or left it (kind: "withdrawal") | The wallet_transaction |
wallet_transaction.returned | A wallet deposit was returned. Produced in sandbox today | The wallet_transaction |
wallet_operation.completed | An authorized send landed | The wallet_operation, with tx_hash |
wallet_operation.failed | An authorized send did not land. Nothing moved | The wallet_operation |
Every kind is <object>.<what happened>, and data.object.object names
the same object as the prefix.
wallet_transaction.created with kind: "deposit" is the one to wire up
first if you use wallets. A wallet is funded by a plain on-chain send, so
this event is how you learn a customer was paid. The alternative is polling
GET /api/wallets/{wallet_id}/history.
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"]}'
event_kinds must name at least one kind, and each must be a kind from
the catalog above: an unknown one returns 422 invalid_parameter with
param: event_kinds. url must be https://, at most 512 characters.
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, newest first, 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. To
backfill after downtime, see
Debugging, replay, and downtime.