Skip to main content

Payload structure

Every webhook has the same envelope. Transaction details live under data. The envelope below shows every field that can appear — only the fields applicable to a given transaction are actually present (see the note).
{
  "event": "transaction.completed",
  "event_id": "8f2a1c44-9b3e-5d61-a2f7-1e9c0b4d6a55",
  "created_at": "2026-06-10T12:00:05.000Z",
  "data": {
    "transaction_id": "…",
    "reference": "…",
    "type": "deposit | payout",
    "status": "pending | processing | sent | completed | failed | rejected | refunded",
    "rail": "fiat | stablecoin",
    "currency": "…",
    "amount": 0,
    "source_amount": 0,
    "source_currency": "…",
    "destination_amount": 0,
    "destination_currency": "…",
    "exchange_rate": 0,
    "fee_amount": 0,
    "description": "…",
    "timestamp": "…",
    "beneficiary": { },
    "network": { },
    "metadata": { }
  }
}
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

FieldDescription
eventEvent type — see Events
event_idStable id for the logical event (use to deduplicate)
created_atWhen the event was generated (ISO 8601)
data.transaction_idRolla transaction id
data.referenceTransaction reference (the external reference)
data.typedeposit or payout
data.statusThe transaction status this event represents — one of pending, processing, sent, completed, failed, rejected, refunded (matches the event type). See Events
data.railfiat or stablecoin
data.amount / data.currencyThe net amount the counterparty receives (in minor units — see Amount format), and its currency — a deposit’s credited amount, or a payout’s amount to the beneficiary. Equals destination_amount.
data.source_amount / data.source_currencyThe debit leg, in minor units. For a payout this is the gross amount, i.e. amount + fee; for FX it’s the source-currency amount.
data.destination_amount / data.destination_currencyThe credit leg, in minor units — what the beneficiary/wallet actually receives.
data.exchange_rateThe effective rate for the conversion, computed as destination_amount / source_amount (destination per source, in minor units) — the same convention as the external transactions API. Present only for FX transactions (source and destination currencies differ); omitted otherwise.
data.fee_amountFee applied, in minor units
data.beneficiaryBeneficiary details — present for payouts, omitted otherwise (see below). Only fields relevant to the payout are included.
data.networkOn-chain details — present only for genuinely on-chain transactions (a real chain and/or wallet address). chain is the full network name (Solana); tx_hash is the on-chain transaction hash. Omitted for transactions with no on-chain leg — including stablecoin-funded payouts that settle to a fiat bank (e.g. a USDT → USD wire).
data.metadataAny metadata you supplied when creating the transaction

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.
CurrencyMinor unitMajor → minor
NGNkobo (1/100)₦100 → 10000
USDcents (1/100)$100 → 10000
XAF1/1001,000 XAF → 100000
USDC1/100100 USDC → 10000
USDT1/100100 USDT → 10000
To display a value, divide by 100 (e.g. amount / 100) for any currency.
These values are not major units. An amount of 10000 on an NGN transaction means ₦100.00, not ₦10,000, and 10000 on a USDC transaction means 100.00 USDC. Treating the integer as a major amount will overstate it by 100×.
Webhook amounts differ from the REST transaction endpoints. Webhook payloads report amounts as major × 100 (e.g. 1000 = 10.00).TheRESTtransactionresponsesGET/wallet/transactions,GET/wallet/transaction/id,andthewithdraw/transfer/swapresponsesreporttransactionamountsinmajorunits(e.g.10=10.00). The REST transaction responses — `GET /wallet/transactions`, `GET /wallet/transaction/{id}`, and the `withdraw` / `transfer` / `swap` responses — report transaction amounts in **major units** (e.g. `10` = 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.
For a payout, amount is what you asked to send (the beneficiary’s amount). source_amount is what’s debited from your wallet, including the fee. So source_amount = amount + fee_amount (when fees are charged on top). All scaled by 100.

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.

Sample: Fiat deposit completed

{
  "event": "transaction.completed",
  "event_id": "0a7b5e21-3c44-5f88-9a1d-77c2e6b0f312",
  "created_at": "2026-06-10T12:00:05.000Z",
  "data": {
    "transaction_id": "866b7abd-6cac-40f2-a04f-d6e58bf47d04",
    "reference": "NG-DEP-9F2K1A",
    "type": "deposit",
    "status": "completed",
    "rail": "fiat",
    "currency": "NGN",
    "amount": 500000,
    "source_amount": 500000,
    "source_currency": "NGN",
    "destination_amount": 500000,
    "destination_currency": "NGN",
    "fee_amount": 0,
    "description": "Deposit from John Doe",
    "timestamp": "2026-06-10T12:00:04.812Z"
  }
}

Sample: Fiat payout completed

{
  "event": "transaction.completed",
  "event_id": "1b8c6f32-4d55-5a99-8b2e-88d3f7c1a423",
  "created_at": "2026-06-10T12:01:10.000Z",
  "data": {
    "transaction_id": "a1f2e3d4-5b6c-7d8e-9f01-23456789abcd",
    "reference": "NG-NFNUJTUW",
    "type": "payout",
    "status": "completed",
    "rail": "fiat",
    "currency": "NGN",
    "amount": 100000,
    "source_amount": 102500,
    "source_currency": "NGN",
    "destination_amount": 100000,
    "destination_currency": "NGN",
    "fee_amount": 2500,
    "description": "Vendor payment",
    "timestamp": "2026-06-10T12:01:09.500Z",
    "beneficiary": {
      "id": "b9f1c2d3-4e5a-6b7c-8d9e-0f1a2b3c4d5e",
      "account_name": "Jane Smith",
      "account_number": "0123456789",
      "bank_name": "Access Bank",
      "bank_code": "000014",
      "currency": "NGN",
      "withdrawal_method": "local_transfer"
    },
    "metadata": { "invoice_id": "INV-2026-042" }
  }
}

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.
{
  "event": "transaction.pending",
  "event_id": "07c97239-a8b0-5579-bd5e-e3221f614173",
  "created_at": "2026-06-10T12:05:00.000Z",
  "data": {
    "transaction_id": "9a56d01f-dc72-4ace-bbbc-a237bdb1c599",
    "reference": "NG-D7NUAS9Q",
    "type": "payout",
    "status": "pending",
    "rail": "fiat",
    "currency": "USD",
    "amount": 100000,
    "source_amount": 160000000,
    "source_currency": "NGN",
    "destination_amount": 100000,
    "destination_currency": "USD",
    "exchange_rate": 0.000625,
    "fee_amount": 250000,
    "description": "FX Payout",
    "timestamp": "2026-06-10T12:04:59.500Z",
    "beneficiary": {
      "id": "79ee09c0-1518-4a5c-86c7-d880a78ec6e5",
      "account_name": "Fatunde Tee",
      "account_number": "0711348929",
      "bank_name": "Bank Of America",
      "routing_number": "026009593",
      "currency": "USD",
      "withdrawal_method": "domestic_wire",
      "bank_address": {
        "street": "17 Akintunde Close",
        "city": "New York",
        "state": "New York",
        "postalCode": "12222",
        "country": "United States"
      }
    }
  }
}
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

{
  "event": "transaction.completed",
  "event_id": "2c9d7a43-5e66-5baa-9c3f-99e4a8d2b534",
  "created_at": "2026-06-10T12:02:30.000Z",
  "data": {
    "transaction_id": "b2c3d4e5-6f70-8192-a3b4-c5d6e7f80912",
    "reference": "5h2k...onchain-signature",
    "type": "deposit",
    "status": "completed",
    "rail": "stablecoin",
    "currency": "USDC",
    "amount": 100000,
    "source_amount": 100000,
    "source_currency": "USDC",
    "destination_amount": 100000,
    "destination_currency": "USDC",
    "fee_amount": 0,
    "description": "USDC deposit",
    "timestamp": "2026-06-10T12:02:29.100Z",
    "network": {
      "chain": "Solana",
      "tx_hash": "5h2k...onchain-signature",
      "source_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
      "destination_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"
    }
  }
}

Sample: Stablecoin payout pending (one-time beneficiary)

{
  "event": "transaction.pending",
  "event_id": "3da08b54-6f77-5cbb-ad40-aaf5b9e3c645",
  "created_at": "2026-06-10T12:03:00.000Z",
  "data": {
    "transaction_id": "c3d4e5f6-7081-9203-b4c5-d6e7f8091234",
    "reference": "USD-WD-7H3M2B",
    "type": "payout",
    "status": "pending",
    "rail": "stablecoin",
    "currency": "USDC",
    "amount": 10000,
    "source_amount": 10000,
    "source_currency": "USDC",
    "destination_amount": 10000,
    "destination_currency": "USDC",
    "fee_amount": 0,
    "description": "Supplier settlement",
    "timestamp": "2026-06-10T12:02:59.700Z",
    "beneficiary": {
      "account_name": "My USDC Wallet",
      "currency": "USDC",
      "withdrawal_method": "crypto_usdc",
      "wallet_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
      "wallet_chain": "Solana"
    },
    "network": {
      "chain": "Solana",
      "tx_hash": "USD-WD-7H3M2B",
      "destination_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"
    },
    "metadata": { "po_number": "PO-99812" }
  }
}

Account event payloads

Account events (account.onboarded, account.submitted, account.approved, account.virtual_account.created) describe an account rather than a transaction, so data carries the account fields. transaction_id is not present.
FieldTypeDescription
data.account_idstringThe account (business or individual) the event is about.
data.namestringAccount name. Omitted if not available.
data.emailstringAccount email. Omitted if not available.
data.entity_typestringbusiness or individual. Omitted if not available.
data.statusstringThe application status (draft, submitted, approved, …). Omitted if not available.
data.virtual_accountobjectPresent on account.virtual_account.created — the provisioned deposit account. Omitted otherwise.

Sample: Account approved

{
  "event": "account.approved",
  "event_id": "9b1c2d3e-4f50-5a61-b273-c8d9e0f1a2b3",
  "created_at": "2026-06-13T10:15:00.000Z",
  "data": {
    "account_id": "a1b2c3d4-e5f6-7081-9203-b4c5d6e7f809",
    "name": "Acme Logistics Ltd",
    "email": "ops@acmelogistics.com",
    "entity_type": "business",
    "status": "approved"
  }
}

Sample: Virtual account created

{
  "event": "account.virtual_account.created",
  "event_id": "7c8d9e0f-1a2b-5c3d-9e4f-5a6b7c8d9e0f",
  "created_at": "2026-06-13T10:16:30.000Z",
  "data": {
    "account_id": "a1b2c3d4-e5f6-7081-9203-b4c5d6e7f809",
    "name": "Acme Logistics Ltd",
    "email": "ops@acmelogistics.com",
    "entity_type": "business",
    "status": "approved",
    "virtual_account": {
      "id": "f0e1d2c3-b4a5-6978-8a9b-0c1d2e3f4a5b",
      "currency": "NGN",
      "method": "bank_transfer",
      "provider": "gtbank",
      "bank_name": "Guaranty Trust Bank",
      "account_number": "1234567890",
      "account_name": "Acme Logistics Ltd",
      "status": "active"
    }
  }
}