Listen for Webhooks
Bank-rail transfers (funding pulls and cash-out pushes) settle asynchronously, so subscribe to webhooks instead of polling. Add a webhook from your Developer Dashboard and subscribe it to the events you care about; you'll get the Signing secret used to verify each delivery.
Available events
Subscribe to any of these from your Developer Dashboard. Every payload is minimal — call the matching GET endpoint with the id for full, viewer-aware detail.
| Event | Fires when |
|---|---|
individual.activated | A player's identity verification is approved and the account goes active |
individual.rejected | A player's identity verification is rejected |
kyx.approved | A KYX submission is approved |
kyx.rejected | A KYX submission is rejected |
payment_method.created | A payment instrument is provisioned or added — bank account, crypto wallet, or virtual account (type discriminates) |
payment_method.updated | A payment instrument's status changes — e.g. a bank account becomes usable on a rail, or registration fails |
balance.updated | An account balance changes — fiat or crypto (currency discriminates); includes a crypto deposit landing |
charge.updated | A card charge resolves (succeeded / failed) — see the Create charge guide |
session.paid | A hosted checkout session is paid — see the Create session guide |
transfer.created · transfer.dispatched · transfer.settled · transfer.returned · transfer.failed | A transfer moves through its lifecycle (funding, payouts, cash-outs — fiat and crypto) |
These cover the whole player lifecycle end to end — onboard (individual.* / kyx.*), provision (payment_method.created), fund (balance.updated), and move money (transfer.*) — so you never need to poll.
Lifecycle events
Payloads for the account, payment-method, and balance events. Each carries only consumer-safe fields.
Individuals & KYX
{ "type": "individual.activated", "event_id": "…", "occurred_on": 1718000000,
"data": { "id": "<individual-account-id>", "status": "active" } }
| Event | data |
|---|---|
individual.activated | { id, status: "active" } |
individual.rejected | { id, status: "rejected" } |
kyx.approved | { account_id, status: "approved" } |
kyx.rejected | { account_id, status: "rejected" } |
Payment methods
{ "type": "payment_method.created", "event_id": "…", "occurred_on": 1718000000,
"data": { "id": "<payment-method-id>", "type": "crypto-wallet",
"account_id": "<account-id>", "currency": "BASE-USDC",
"on_chain_address": "0x…" } }
payment_method.created—data.typeisbank-account(external bank or virtual account) orcrypto-wallet. A crypto wallet also carriescurrency+on_chain_address; a bank account carriescurrency.payment_method.updated—{ id, type, status }, wherestatusisactive(registered on a rail, ready to use) orregistration_failed.
Fetch the full instrument (routing/last4, deposit address, etc.) with GET /payment-methods/{id}.
Balances
{ "type": "balance.updated", "event_id": "…", "occurred_on": 1718000000,
"data": { "account_id": "<account-id>", "currency": "BASE-USDC",
"amount": 100000000, "type": "credit", "available": 100000000 } }
currency discriminates fiat (USD) from crypto (BASE-USDC). amount is in minor units. A crypto deposit landing fires this with the crypto currency and type: "credit" — the signal that a wallet/virtual-account deposit settled.
Transfer events
| Event | Fires when | Act on it |
|---|---|---|
transfer.created | A transfer is accepted (incl. instant internal posted) | Record it |
transfer.dispatched | Handed to the bank rail (in-transit) | Informational |
transfer.settled | Funds landed — money is final | Mark the deposit / cash-out complete |
transfer.returned | The bank returned it after dispatch; the debit is reversed | Reconcile / notify the player |
transfer.failed | Could not be dispatched | Retry / notify |
You're notified for transfers on either side of the movement — both your platform (merchant wallet) and the player's onboarding partner are notified, so you see funding, payouts, and player cash-outs.
Example payload
{
"type": "transfer.settled",
"event_id": "c097c7c2-c6dc-44d8-bf98-75d2a4ee6a45",
"occurred_on": 1718000100,
"data": {
"id": "0a9b8c7d-1234-4e8b-9c7a-2f6d8e0a1b22",
"status": "settled",
"amount": 8000,
"currency": "USD",
"reference": "cash out",
"direction": "outgoing"
}
}
| Property | Possible values | Description |
|---|---|---|
| type | transfer.created | transfer.dispatched | transfer.settled | transfer.returned | transfer.failed | Name of the event |
| event_id | UUID | The ID of the specific event |
| occurred_on | int | Unix timestamp when the event took place |
| data.id | UUID | The transfer ID |
| data.status | posted | pending | dispatched | settled | returned | failed | Current status of the transfer |
| data.amount | int | Amount in the smallest currency unit (cents) |
| data.currency | string | ISO 4217 currency code |
| data.reference | string | null | Your reference, if set |
| data.direction | outgoing | incoming | Relative to the notified account |
The payload is intentionally minimal. For full, viewer-aware detail call GET /transfers/{id} with the transfer ID — see the Get a transfer API reference.
transfer.returned tells you a transfer was returned, but the underlying bank return code is not exposed in the payload. Treat a return as "reconcile and notify", and contact support if you need the specific reason.
Webhook security
Each delivery includes headers for signature verification:
- X-Nonce: 086b9d3f607a4b3e2e2818f5db3c1ff5
- X-Signature: 62ad115f0b8ef012b07d1b7fc…
- X-Timestamp: 1664411630
- X-Webhook-Id: 0551615f-2cb5-4cfd-865e-75bb24cc6219
Verify with your webhook's Signing Secret:
PHP:
$secret = '123123123';
$concat_header = "$timestamp|$nonce|$webhook_id";
$signature = hash_hmac('sha512', $concat_header, $secret);
Python:
secret = '123123123'
concat_header = f"{timestamp}|{nonce}|{webhook_id}"
signature = hmac.new(secret.encode(), concat_header.encode(), hashlib.sha512).hexdigest()
Go:
secret := "123123123"
concatHeader := fmt.Sprintf("%s|%s|%s", timestamp, nonce, webhookID)
h := hmac.New(sha512.New, []byte(secret))
h.Write([]byte(concatHeader))
signature := hex.EncodeToString(h.Sum(nil))
Compare the computed signature to X-Signature to confirm authenticity.