Skip to main content

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.

EventFires when
individual.activatedA player's identity verification is approved and the account goes active
individual.rejectedA player's identity verification is rejected
kyx.approvedA KYX submission is approved
kyx.rejectedA KYX submission is rejected
payment_method.createdA payment instrument is provisioned or added — bank account, crypto wallet, or virtual account (type discriminates)
payment_method.updatedA payment instrument's status changes — e.g. a bank account becomes usable on a rail, or registration fails
balance.updatedAn account balance changes — fiat or crypto (currency discriminates); includes a crypto deposit landing
charge.updatedA card charge resolves (succeeded / failed) — see the Create charge guide
session.paidA hosted checkout session is paid — see the Create session guide
transfer.created · transfer.dispatched · transfer.settled · transfer.returned · transfer.failedA 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" } }
Eventdata
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.createddata.type is bank-account (external bank or virtual account) or crypto-wallet. A crypto wallet also carries currency + on_chain_address; a bank account carries currency.
  • payment_method.updated{ id, type, status }, where status is active (registered on a rail, ready to use) or registration_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

EventFires whenAct on it
transfer.createdA transfer is accepted (incl. instant internal posted)Record it
transfer.dispatchedHanded to the bank rail (in-transit)Informational
transfer.settledFunds landed — money is finalMark the deposit / cash-out complete
transfer.returnedThe bank returned it after dispatch; the debit is reversedReconcile / notify the player
transfer.failedCould not be dispatchedRetry / 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"
}
}
PropertyPossible valuesDescription
typetransfer.created | transfer.dispatched | transfer.settled | transfer.returned | transfer.failedName of the event
event_idUUIDThe ID of the specific event
occurred_onintUnix timestamp when the event took place
data.idUUIDThe transfer ID
data.statusposted | pending | dispatched | settled | returned | failedCurrent status of the transfer
data.amountintAmount in the smallest currency unit (cents)
data.currencystringISO 4217 currency code
data.referencestring | nullYour reference, if set
data.directionoutgoing | incomingRelative 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.

Return reasons

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.