Payload structure
Every webhook has the same envelope. Transaction details live underdata. The envelope below shows every field that can appear — only the fields applicable to a given transaction are actually present (see the note).
The payload carries only the fields relevant to the transaction — inapplicable fields are omitted entirely, not returned as
null. This applies at every level: a fiat bank payout has no network, exchange_rate, wallet_address or wallet_chain; a crypto payout has no bank fields; a deposit has no beneficiary. So a field’s absence simply means it doesn’t apply. The only exception is metadata, which is passed through exactly as you supplied it. Match transactions by transaction_id (or reference), and treat any missing field as not applicable.Field reference
Amount format
All monetary fields —amount, source_amount, destination_amount, and fee_amount — are integers equal to the major-currency value × 100 (two implied decimal places). This is the same scale for every currency, fiat and stablecoin alike — to get the human-readable amount, always divide by 100.
To display a value, divide by 100 (e.g.
amount / 100) for any currency.
Webhook amounts differ from the REST transaction endpoints. Webhook payloads report amounts as major × 100 (e.g.
1000 = 10.00). So the same transaction shows 1000 in its webhook and 10 over REST. Match a webhook to a REST record by transaction_id (or reference), not by comparing raw amounts, and apply the right scale for each source.Fees paid by another account
Your account can be configured so that fees on transactions are charged to a different account of yours rather than deducted from each transaction. When that applies, the fee is booked as its own transaction on the paying account, and the payout carries a link to it:- The fee does not arrive as its own webhook. Only deposits and payouts emit webhooks — fee transactions never do. The link above is the only notification you receive.
source_amountexcludes the fee here, because the fee never touched this transaction. It equalsamount.- On the fee transaction itself,
fee_amountis0and the fee value is carried as itssource_amount/destination_amount. This keeps the fee from being counted twice when totallingfee_amountacross transactions. Read the fee from the payout’sfee_amount.
GET /wallet/transactions) returns it with related_transaction_id and related_reference pointing back at the payout, so you can reconcile from either direction.
Both fields are omitted entirely — not
null — when no separate fee transaction exists. Branch on their presence. They were introduced on 18 August 2026; webhooks you captured before then do not carry them even where a fee transaction exists.The beneficiary object (payouts)
Drawn from this whitelisted set — but only the fields relevant to the payout are returned. Empty/null fields are omitted, so a fiat bank payout returns bank-account fields (no wallet fields), while a crypto payout returns wallet fields (no bank fields). Internal fields (your business_id, row timestamps) are never included.
id · account_name · account_number · bank_name · bank_code · bank_address · currency · withdrawal_method · routing_number · swift_code · iban · bic · sort_code · intermediary_bank_name · intermediary_bank_routing_number · wallet_address · wallet_chain · email · contact_person · label · beneficiary_address
wallet_chain is the full network name (Solana). Inside bank_address / beneficiary_address, country is the full country name (e.g. Côte d'Ivoire, not CI). A present id means the payout went to a saved beneficiary (it’s the beneficiary’s UUID). One-time (inline) beneficiaries have no id field at all — use its absence to detect them.The virtual_account object (deposits)
The destination account a deposit was received into. For a bank (fiat) deposit: id (the virtual account’s Rolla id), currency, method (bank_transfer), provider, bank_name, account_number, account_name, and — when the account has them — routing_number / swift_code. For a stablecoin deposit: id, currency, method (crypto), provider, wallet_address, network. Empty fields are omitted.
The payer object (deposits)
Who sent the deposit, as reported by the provider. Fields: name, account_number, bank_name, country — only the ones the provider supplies are included, so availability varies by rail:
- USD — always
nameandaccount_number;bank_nameandcountryare included when the sending rail reports them.
payer is omitted when the provider sends no sender details. Crypto deposits expose the sender via the network.source_address field instead of payer.
Sample: Fiat deposit completed
Sample: Deposit into a customer virtual account
Same shape as a fiat deposit, pluscustomer_identifier — the identifier you set when creating the account, so you can credit the right end-user. Only deposits into a customer virtual account include it.
Sample: Fiat payout completed
Sample: FX payout (NGN → USD) pending
A cross-currency payout.exchange_rate is populated (the quoted rate for the pair), and the wire beneficiary’s bank_address.country is the full country name. Only the bank-relevant beneficiary fields are present.
Here
exchange_rate is destination_amount / source_amount = 100000 / 160000000 = 0.000625 (USD minor units per NGN minor unit). Use source_amount and destination_amount for exact reconciliation — they are the authoritative debit and credit legs in minor units.Sample: Stablecoin deposit completed
Sample: Stablecoin payout pending (one-time beneficiary)
Account event payloads
Account events (account.onboarded, account.submitted, account.approved, account.rejected, account.changes_requested, account.virtual_account.created, account.verification.completed) describe an account rather than a transaction, so data carries the account fields. transaction_id is not present.
Sample: Account approved
Sample: Account rejected
review.reason is the reviewer’s own wording — there is no separate email, so this payload is the only notification carrying it.
Sample: Changes requested
The account reopens for editing. Fix whatreview.reason describes and resubmit — the account then emits account.submitted again.
review is omitted entirely when a decision was recorded without a note, so treat it as optional.Sample: Virtual account created
Sample: Verification completed
Sent when an identity check finishes, so you learn the result instead of polling Get Requirements.A passed check does not mean the account is submittable —
ready_to_submit above is false because the application is still missing data. Call Get Requirements and read submitBlocker for what remains.related_person_id naming who cleared. ready_to_submit stays false until the last of them is done.