Platform Overview
The full-stack payment platform purpose-built for gaming
ZBD is a bi-directional payment platform for game studios. It handles everything from paying players real money for in-game achievements to processing purchases and managing virtual currency — all while keeping studios completely out of PII and PCI scope. Built on regulatory licenses (US Money Transmitter Licenses + EU MiCAR/EMI) and micropayment economics that make sub-cent transactions viable, ZBD enables product categories that are structurally impossible on traditional payment rails.
Three Workstreams
Payout to Bank
Studios trigger bank payouts on behalf of players. ZBD handles banking rails, compliance screening, and settlement. ACH, instant via Lightning.
Wallet Loading + Checkout
Players fund wallets from linked bank accounts or purchase items via a hosted checkout widget. Studios bypass app store fees with direct-to-consumer web stores.
Virtual Currency + Tournaments
Convertible virtual currency ledger with cash-out support. Skill-based tournaments with escrow, geo-fencing, and automated prize distribution.
Competitive Landscape
| Competitor | Pay-In | Pay-Out | Gaming-Native | Micropayments | Licensing |
|---|---|---|---|---|---|
| Xsolla | 700+ methods | No | Yes | No | MoR model |
| Tipalti | No | 196 countries | No | No | Via partners |
| Stripe | Yes | Yes | No | No ($0.50 min) | Yes |
| Coinflow | Yes (crypto+fiat) | Yes | Partial | Yes (crypto) | Growing ($25M Series A) |
| Nuvei | Yes | Yes | iGaming only | No | Yes (gambling) |
| Skillz | Tournaments only | Tournaments only | Yes | No | Skill gaming |
| ZBD | Yes (checkout) | Yes (payouts) | Yes | Yes ($0.0005) | MTL + MiCAR + EMI |
The Compound Moat
"Three things that compound together: (1) Regulatory licenses that took years and millions — competitors need 2+ years just to get started. (2) The only bi-directional payment platform purpose-built for gaming — Xsolla does pay-in, Tipalti does pay-out, ZBD does both. (3) Micropayment economics that enable use cases structurally impossible on traditional rails — you can't reward a player $0.02 through Stripe. No single piece is unassailable, but together they create a 3+ year head start."
Shared Infrastructure
Authentication, response formats, webhooks, and conventions shared across all endpoints
Authentication & Security
All requests are scoped to a project and authenticated via the apikey header.
curl -H "apikey: your_project_api_key" \
https://api.zbdpay.com/v1/project/balance
Keys can be restricted to specific permissions: payout:read, payout:write, user:read, ledger:write. Production keys should use minimum required scopes.
POST /v1/api-keys/rotate enables zero-downtime rotation. The old key remains valid for 24 hours after rotation.
Production keys support IP allowlisting to restrict API access to known server addresses.
X-ZBD-Signature header with HMAC-SHA256 of the raw request body using your webhook secret.
Idempotency-Key header on POST requests prevents duplicate operations. Keys are unique per-project and expire after 24 hours.
Base URL
https://api.zbdpay.com/v1
Response Envelope
All responses use a standard envelope:
{
"success": true,
"message": "Human-readable status",
"data": { ... }
}
Error Format
{
"success": false,
"message": "Human-readable error description",
"error": {
"code": "kyc_required",
"type": "invalid_request",
"param": "userId",
"retryable": false
}
}
Rate Limiting
All responses include rate limit headers. Default: 1,000 requests/minute per API key.
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 997
X-RateLimit-Reset: 1679529600
Sandbox vs. Production
Sandbox Base URL: https://sandbox.api.zbdpay.com/v1
- All sandbox object IDs prefixed with
test_(e.g.,test_po_m4n5o6p7) - Includes test fixtures: pre-verified users, simulated KYC flows
- Trigger amounts:
$9.99always fails,$1.00always succeeds
Project Management
| Method | Endpoint | Description |
|---|---|---|
| GET | /v1/project/balance | Get your project's current balance (available funds for payouts + prize pools). |
| GET | /v1/project/transactions | Project-level transaction history (top-ups, payouts, fees, escrow holds). |
Webhook Management
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/webhooks | Register a webhook endpoint URL. Specify which events to subscribe to. |
| GET | /v1/webhooks | List registered webhook endpoints. |
| PATCH | /v1/webhooks/{webhookId} | Update a webhook endpoint (URL, events, active/inactive). |
| DELETE | /v1/webhooks/{webhookId} | Delete a webhook endpoint. |
| POST | /v1/webhooks/{webhookId}/test | Send a test event to verify your endpoint is receiving correctly. |
This is an upgrade from ZBD's current per-request callbackUrl pattern to global webhook subscriptions (Stripe-style). The callbackUrl pattern is still supported for backwards compatibility.
Webhook Delivery Guarantees
eventId field to deduplicate.timestamp and object status to determine current state.GET /v1/events returns a log of all events. Filter by type, createdAfter, createdBefore. Use this to replay missed events during outages.Multi-Currency Support
All monetary amounts use the smallest unit of the specified currency (cents for USD, centimes for EUR). The currency field accepts ISO 4217 codes. Supported at launch: usd, eur.
- Wallet balances are per-currency (a user can hold both USD and EUR)
- Cross-currency payouts supported: ZBD handles FX conversion, disclosed via
fxRateandfxFeefields - Tournament entry fees and prizes must be in a single currency per tournament
Users & Identity
Registration, KYC verification, and user management
Zero PII — Developers never handle identity documentsEndpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/users | Register a player. Accepts externalId, email, country. Returns a ZBD userId. |
| GET | /v1/users/{userId} | Get user profile (KYC status, tier, linked accounts). |
| GET | /v1/users | List users. Filter by kycStatus, country, createdAfter. |
| POST | /v1/users/{userId}/verify | Initiate KYC verification. Returns a verificationUrl (hosted by ZBD). |
| GET | /v1/users/{userId}/verify/status | Check current KYC verification status. |
KYC Tiers
Tiered verification minimizes friction and maximizes conversion. Players earn first, verify only at cash-out.
| Tier | Requirements | Limits | Use Case |
|---|---|---|---|
| Tier 0 | Email + phone only | Earn virtual currency, no cash-out | In-game economy participation |
| Tier 1 | + Name, DOB, country | Payouts up to $200/month | Casual player rewards |
| Tier 2 | + Government ID, address | Payouts up to $2,000/month | Regular earners |
| Tier 3 | + SSN/TIN, enhanced due diligence | Unlimited (with transaction monitoring) | High-volume creators/streamers |
User Object
{
"id": "usr_a1b2c3d4",
"externalId": "player_12345",
"email": "player@example.com",
"country": "US",
"kycStatus": "approved",
"kycTier": 2,
"createdAt": "2026-03-20T10:00:00Z",
"metadata": {}
}
Webhooks
user.created— New user registereduser.kyc.submitted— Player started verificationuser.kyc.approved— Verification passed (includeskycTier)user.kyc.rejected— Verification failed (includesreason)user.kyc.tier_upgraded— User qualified for higher tier
Payout to Bank
Trigger bank account payouts on behalf of players
Zero PII — Developers never see bank account numbersBank Account Linking
Before a payout can be created, the player must link a bank account through ZBD's hosted Plaid flow.
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/users/{userId}/bank-accounts | Create a bank account linking session. Returns linkUrl (ZBD-hosted, powered by Plaid). |
| GET | /v1/users/{userId}/bank-accounts | List linked bank accounts. Returns tokenized references only. |
| GET | /v1/users/{userId}/bank-accounts/{bankAccountId} | Get bank account details (masked: ****6789, institution, status). |
| DELETE | /v1/users/{userId}/bank-accounts/{bankAccountId} | Unlink a bank account. |
Bank Account Object (tokenized)
{
"id": "ba_x9y8z7",
"userId": "usr_a1b2c3d4",
"institutionName": "Chase",
"accountType": "checking",
"last4": "6789",
"status": "verified",
"createdAt": "2026-03-20T12:00:00Z"
}
Developer never sees: full account number, routing number, holder name, or any PII.
Payout Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/payouts | Create a payout. Specify userId, bankAccountId, amount (cents), currency. |
| GET | /v1/payouts/{payoutId} | Retrieve payout details and current status. |
| GET | /v1/payouts | List payouts. Filter by userId, status, date range. Cursor-paginated. |
| POST | /v1/payouts/{payoutId}/cancel | Cancel a payout (only if status is pending). |
| POST | /v1/payouts/batch | Create up to 500 payouts in one request. Returns a batchId. |
| GET | /v1/payouts/batch/{batchId} | Get batch status and individual payout statuses. |
Create Payout Request
POST /v1/payouts
{
"userId": "usr_a1b2c3d4",
"bankAccountId": "ba_x9y8z7",
"amount": 5000,
"currency": "usd",
"description": "Tournament winnings - March 2026",
"internalId": "game_match_789",
"metadata": {
"gameId": "battle-royale-2",
"matchId": "match_789",
"reason": "tournament_prize"
}
}
Payout Object
{
"id": "po_m4n5o6p7",
"userId": "usr_a1b2c3d4",
"bankAccountId": "ba_x9y8z7",
"amount": 5000,
"currency": "usd",
"status": "processing",
"description": "Tournament winnings - March 2026",
"internalId": "game_match_789",
"method": "ach",
"estimatedArrival": "2026-03-22T00:00:00Z",
"failureCode": null,
"failureMessage": null,
"createdAt": "2026-03-20T14:00:00Z",
"completedAt": null,
"metadata": {
"gameId": "battle-royale-2",
"matchId": "match_789",
"reason": "tournament_prize"
}
}
Payout Status Lifecycle
Failure Codes
| Code | Description | Retryable |
|---|---|---|
insufficient_project_balance | Project doesn't have enough funds to cover payout | Yes (after top-up) |
kyc_required | User hasn't completed required KYC tier | No (redirect user to verify) |
kyc_pending | KYC submitted but not yet approved | Yes (after approval) |
kyc_rejected | KYC verification failed | No |
payout_limit_exceeded | Exceeds user's tier limit | No (upgrade tier) |
bank_account_not_verified | Bank account linking not complete | No |
invalid_bank_account | Bank account details invalid | No (re-link) |
bank_account_closed | Destination account is closed | No (re-link) |
account_frozen | ZBD flagged account for review | No (contact support) |
sanctions_match | User flagged by sanctions screening | No |
duplicate_payout | Idempotency key already used | No |
amount_too_small | Below minimum payout ($1.00) | No |
amount_too_large | Exceeds single-payout maximum | No (split into multiple) |
velocity_limit | Too many payouts in time window | Yes (after cooldown) |
internal_error | ZBD system error | Yes (with backoff) |
Webhooks
payout.pending— Payout created, awaiting processingpayout.processing— Submitted to banking partnerpayout.in_transit— Funds left ZBD, in transit to bankpayout.completed— Funds deposited successfullypayout.failed— Payout failed (includesfailureCode+failureMessage)payout.canceled— Payout canceled by developerpayout.returned— Bank returned the deposit after completion
Code Samples — Node.js
Create a Payout
import ZBD from '@zbd/node';
const client = new ZBD('your_api_key');
// Create a payout (idempotency key goes in the options, sent as header)
const payout = await client.payouts.create({
userId: 'usr_a1b2c3d4',
bankAccountId: 'ba_x9y8z7',
amount: 5000,
currency: 'usd',
description: 'Tournament winnings',
internalId: 'match_789',
metadata: { gameId: 'battle-royale-2' }
}, {
idempotencyKey: 'payout_unique_abc123'
});
console.log(payout.data.id); // "po_m4n5o6p7"
console.log(payout.data.status); // "pending"
Webhook Handler
import express from 'express';
import ZBD from '@zbd/node';
const app = express();
const client = new ZBD('your_api_key');
// IMPORTANT: Use express.raw() to get the raw body for signature verification
app.post('/webhooks/zbd', express.raw({ type: 'application/json' }), async (req, res) => {
const signature = req.headers['x-zbd-signature'];
// req.body is a raw Buffer here — required for HMAC verification
if (!client.webhooks.verify(req.body, signature)) {
return res.status(401).send('Invalid signature');
}
const { event, data } = JSON.parse(req.body);
switch (event) {
case 'payout.completed':
await markPayoutComplete(data.internalId, data.amount);
break;
case 'payout.failed':
await handlePayoutFailure(data.internalId, data.failureCode);
break;
case 'payout.returned':
// Bank returned funds — re-credit player's in-game balance
await reverseGamePayout(data.internalId, data.amount);
break;
}
res.status(200).send('OK');
});
Wallet Loading
Allow players to fund a ZBD wallet from a linked bank account
Zero PII — Sensitive bank data stays inside ZBD's perimeterEndpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /v1/users/{userId}/wallet | Get wallet balance and status. |
| POST | /v1/users/{userId}/wallet/load | Initiate a wallet load from a linked bank account. |
| GET | /v1/users/{userId}/wallet/loads/{loadId} | Get load status. |
| GET | /v1/users/{userId}/wallet/loads | List wallet loads. Filter by status, date range. |
| GET | /v1/users/{userId}/wallet/transactions | Wallet transaction history (loads, debits, transfers). |
Wallet Object
{
"userId": "usr_a1b2c3d4",
"balance": 15000,
"currency": "usd",
"status": "active",
"dailyLoadLimit": 50000,
"dailyLoadRemaining": 35000,
"updatedAt": "2026-03-20T15:00:00Z"
}
Create Wallet Load Request
POST /v1/users/{userId}/wallet/load
{
"bankAccountId": "ba_x9y8z7",
"amount": 2500,
"currency": "usd",
"internalId": "load_session_456"
}
Wallet Load Object
{
"id": "wl_q1r2s3t4",
"userId": "usr_a1b2c3d4",
"bankAccountId": "ba_x9y8z7",
"amount": 2500,
"currency": "usd",
"status": "pending",
"internalId": "load_session_456",
"createdAt": "2026-03-20T15:30:00Z",
"completedAt": null
}
Load Status Lifecycle
SDK vs. Developer Responsibility
| SDK Handles (inside ZBD) | Developer Receives |
|---|---|
| Bank auth via Plaid Link | Tokenized bankAccountId |
| ACH debit initiation | Load status + webhooks |
| Reg E compliance (error resolution) | load.failed with reason code |
| Fraud screening on load | Approved/rejected decision |
| Balance updates | Updated wallet balance via API |
Webhooks
wallet.load.pending— Load initiatedwallet.load.completed— Funds added to walletwallet.load.failed— Load failed (includes reason)wallet.balance.updated— Balance changed (any reason)
Error Codes
| Code | Description | Retryable |
|---|---|---|
bank_account_not_linked | No verified bank account | No (link first) |
insufficient_bank_funds | Player's bank account has insufficient funds | Yes (after player adds funds) |
load_limit_exceeded | Daily/monthly load limit reached | Yes (after limit resets) |
kyc_required | KYC tier insufficient for wallet loading | No (upgrade tier) |
wallet_suspended | Wallet suspended for review | No |
duplicate_load | Idempotency collision | No |
Checkout (Web Store)
Hosted checkout widget for direct-to-consumer purchases, bypassing app store fees
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/checkout/sessions | Create a checkout session. Returns a checkoutUrl. |
| GET | /v1/checkout/sessions/{sessionId} | Get session status and payment details. |
| GET | /v1/checkout/sessions | List checkout sessions. Filter by userId, status, date range. |
| POST | /v1/checkout/sessions/{sessionId}/refund | Refund a completed purchase. |
Create Checkout Session
POST /v1/checkout/sessions
{
"userId": "usr_a1b2c3d4",
"lineItems": [
{
"name": "1000 Gold Coins",
"description": "In-game currency pack",
"amount": 999,
"currency": "usd",
"quantity": 1,
"metadata": { "sku": "gc_1000", "type": "virtual_currency" }
}
],
"successUrl": "https://yourgame.com/purchase/success?session={SESSION_ID}",
"cancelUrl": "https://yourgame.com/purchase/cancel",
"internalId": "purchase_abc123",
"metadata": { "gameId": "battle-royale-2" }
}
Checkout Session Object
{
"id": "cs_v1w2x3",
"userId": "usr_a1b2c3d4",
"status": "completed",
"paymentMethod": "card",
"amountTotal": 999,
"currency": "usd",
"lineItems": [ ... ],
"internalId": "purchase_abc123",
"createdAt": "2026-03-20T17:00:00Z",
"completedAt": "2026-03-20T17:01:30Z"
}
Checkout Flow
- 1. Create session — Developer creates checkout session via API, receives a
checkoutUrl. - 2. Redirect player — Player is redirected to ZBD's hosted checkout page (card entry, Apple Pay, Google Pay).
- 3. Process payment — ZBD processes payment (PCI-compliant, fraud screening, 3D Secure).
- 4. Fulfill order — Webhook
checkout.completedfires. Developer credits items/currency to the player. Funds settle to the studio's ZBD project balance.
Platform ToS Strategy
Web store checkout happens outside the app. Player opens a link in their browser.
- Post-Epic v. Apple (April 2025 ruling): developers can link to external purchase pages.
- The December 2025 Ninth Circuit appeal allows Apple to charge a "reasonable commission" (exact rate TBD by district court). Even so, studios save meaningfully vs. 30%.
- In the EU, the DMA separately prohibits Apple from charging commissions on external links, making the web checkout value prop strongest there.
- In-app, the studio can still use Apple/Google IAP for platform-required purchases. ZBD web checkout is the direct-to-consumer alternative.
Webhooks
checkout.completed— Payment succeeded, credit items to playercheckout.failed— Payment failedcheckout.refunded— Refund processedcheckout.dispute.created— Chargeback initiatedcheckout.dispute.resolved— Chargeback resolved (won or lost)
Virtual Currency
Convertible virtual currency ledger for in-game economies, backed by ZBD as system of record
Currency Configuration
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/currencies | Define a virtual currency (name, symbol, conversion rate, limits). |
| GET | /v1/currencies/{currencyId} | Get currency config. |
| PATCH | /v1/currencies/{currencyId} | Update currency settings (rate, limits). |
| GET | /v1/currencies/{currencyId}/rate | Get current conversion rate to USD. |
Currency Object
{
"id": "cur_g1h2i3",
"name": "Gold Coins",
"symbol": "GC",
"conversionRate": 100,
"conversionUnit": "usd_cents",
"conversionDirection": "to_fiat",
"minConversion": 1000,
"maxConversionPerDay": 50000,
"status": "active",
"createdAt": "2026-03-01T00:00:00Z"
}
conversionRate: 100 means 100 Gold Coins = $1.00 USD. Developer defines earn rate in-game; ZBD defines the cash-out rate.
Ledger Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/ledger/credit | Credit virtual currency to a user (player earned coins). |
| POST | /v1/ledger/debit | Debit virtual currency (player spent coins in-game). |
| POST | /v1/ledger/transfer | Player-to-player transfer. |
| GET | /v1/ledger/balance/{userId} | Get player's virtual currency balance. |
| GET | /v1/ledger/transactions | Transaction history. Filter by userId, type, date range. |
| POST | /v1/conversions | Convert virtual currency to USD. Requires KYC. Credits user's ZBD wallet. |
| GET | /v1/conversions/{conversionId} | Get conversion status. |
| GET | /v1/conversions | List conversions. |
Credit Request (player earns coins)
POST /v1/ledger/credit
{
"userId": "usr_a1b2c3d4",
"currencyId": "cur_g1h2i3",
"amount": 500,
"reason": "match_victory",
"internalId": "match_789_reward",
"metadata": {
"matchId": "match_789",
"placement": 1
}
}
Ledger Transaction Object
{
"id": "txn_k5l6m7",
"type": "credit",
"userId": "usr_a1b2c3d4",
"currencyId": "cur_g1h2i3",
"amount": 500,
"balanceAfter": 3200,
"reason": "match_victory",
"internalId": "match_789_reward",
"createdAt": "2026-03-20T16:00:00Z",
"metadata": {
"matchId": "match_789",
"placement": 1
}
}
Conversion Request (player cashes out)
POST /v1/conversions
{
"userId": "usr_a1b2c3d4",
"currencyId": "cur_g1h2i3",
"amount": 2000,
"destination": "wallet"
}
Converts 2000 Gold Coins to $20.00 USD, credited to user's ZBD wallet. From there, the player can use the Payout to Bank API to withdraw.
Key Design Decisions
1. Developer doesn't own the ledger
ZBD is system of record. Developer calls credit/debit APIs, but can't directly manipulate balances. This prevents fraud and ensures auditability.
2. Conversion is one-way (virtual to fiat)
The virtual currency is "convertible" under FinCEN guidance (FIN-2013-G001), which means ZBD operates as a licensed money transmitter for the conversion. One-way conversion simplifies the regulatory model because ZBD is not operating a two-sided exchange/marketplace — but it still requires ZBD's MSB registration and state MTLs. This is why ZBD's licensing is the moat: competitors need 2+ years of licensing before they can legally offer this.
3. Conversion requires KYC
Because this is the point where virtual currency becomes real money. FinCEN requires identity verification for convertible virtual currency transactions.
4. Conversion flows through the wallet
Not directly to bank. This creates a clean two-step: convert then withdraw. Each step has its own compliance checks.
5. Anti-fraud built into the ledger
Velocity checks (max credits per hour), minimum conversion thresholds, suspicious pattern detection — all handled by ZBD.
6. Double-entry accounting
Every credit to a player is a debit from the project's reward pool. Developer tops up the pool; ZBD tracks both sides.
Webhooks
ledger.credited— Virtual currency credited to userledger.debited— Virtual currency debited from userledger.transfer.completed— P2P transfer completedconversion.pending— Conversion requestedconversion.completed— Conversion processed, wallet creditedconversion.failed— Conversion failed (includes reason)
Error Codes
| Code | Description | Retryable |
|---|---|---|
insufficient_virtual_balance | User doesn't have enough virtual currency | No |
conversion_limit_exceeded | Daily conversion limit reached | Yes (after reset) |
below_minimum_conversion | Amount below minimum conversion threshold | No |
kyc_required | KYC tier insufficient for conversion | No |
currency_not_found | Invalid currency ID | No |
reward_pool_depleted | Project's reward pool is empty | Yes (after top-up) |
velocity_limit | Too many ledger operations in time window | Yes (after cooldown) |
Tournaments
Skill-based tournaments with buy-in escrow, automated prize distribution, and geo-fencing
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/tournaments | Create a tournament with entry fee, prize structure, and eligibility rules. |
| GET | /v1/tournaments/{tournamentId} | Get tournament details and status. |
| GET | /v1/tournaments | List tournaments. Filter by status, currencyType, date range. |
| POST | /v1/tournaments/{tournamentId}/enter | Enter a player. Debits entry fee from wallet (cash) or ledger (virtual). |
| POST | /v1/tournaments/{tournamentId}/results | Submit final results/rankings. Triggers prize distribution. |
| POST | /v1/tournaments/{tournamentId}/cancel | Cancel tournament. Refunds all entry fees. |
| GET | /v1/tournaments/{tournamentId}/entries | List all entries/participants. |
| GET | /v1/tournaments/{tournamentId}/payouts | List prize payouts for a completed tournament. |
Create Tournament
POST /v1/tournaments
{
"name": "Friday Night Showdown",
"gameId": "battle-royale-2",
"entryFee": 500,
"currency": "usd",
"maxPlayers": 100,
"minPlayers": 10,
"prizeStructure": [
{ "rank": 1, "percentage": 50, "label": "1st Place" },
{ "rank": 2, "percentage": 30, "label": "2nd Place" },
{ "rank": 3, "percentage": 20, "label": "3rd Place" }
],
"platformFeePercent": 10,
"prizeSource": "entry_fees",
"startsAt": "2026-03-21T20:00:00Z",
"endsAt": "2026-03-21T22:00:00Z",
"eligibleRegions": ["US_SKILL_GAMING"],
"metadata": { "season": "3", "week": "12" }
}
Tournament Object
{
"id": "trn_d4e5f6",
"name": "Friday Night Showdown",
"status": "active",
"entryFee": 500,
"currency": "usd",
"currentPlayers": 47,
"maxPlayers": 100,
"minPlayers": 10,
"prizePool": 21150,
"prizeStructure": [ ... ],
"prizeSource": "entry_fees",
"startsAt": "2026-03-21T20:00:00Z",
"endsAt": "2026-03-21T22:00:00Z",
"eligibleRegions": ["US_SKILL_GAMING"],
"createdAt": "2026-03-20T10:00:00Z",
"metadata": { "season": "3", "week": "12" }
}
Tournament Status Lifecycle
Enter Tournament Flow
POST /v1/tournaments/{tournamentId}/enter
{
"userId": "usr_a1b2c3d4",
"internalId": "entry_player123_trn_d4e5f6"
}
ZBD automatically:
- Checks player eligibility (KYC tier, age 18+, geo-location in eligible region)
- Debits entry fee from player's wallet
- Holds entry fee in escrow until tournament completes
- Returns entry confirmation with
entryId
Submit Results Flow
POST /v1/tournaments/{tournamentId}/results
{
"rankings": [
{ "userId": "usr_a1b2c3d4", "rank": 1, "score": 9850 },
{ "userId": "usr_e5f6g7h8", "rank": 2, "score": 9200 },
{ "userId": "usr_i9j0k1l2", "rank": 3, "score": 8700 }
]
}
ZBD automatically:
- Validates rankings (all entrants accounted for)
- Distributes prizes per
prizeStructure— credits to winners' wallets - Takes platform fee from prize pool (e.g., 10%)
- Fires
tournament.settledwebhook with full payout details
Prize Source Options
| Source | How it Works | Best For |
|---|---|---|
entry_fees | Prize pool = sum of entry fees minus platform fee. Skillz model. | Competitive esports, PvP games |
developer_funded | Studio pre-funds the prize pool from project balance. Entry fee can be $0. | Promotional events, player acquisition |
hybrid | Base prize from developer + bonus from entry fees. | Seasonal tournaments, live events |
Escrow Model
- Entry fees are held in a ZBD-managed escrow account (not the developer's project balance).
- Developer cannot access escrowed funds.
- On tournament completion: prizes distributed from escrow, remainder (fees) split between developer + ZBD.
- On cancellation: full refund to all entrants from escrow.
- This protects players and satisfies regulatory requirements for skill gaming.
Virtual Currency Tournaments
Same API, but with currency: "virtual" and a currencyId.
- No geo-restrictions (virtual currency tournaments legal everywhere)
- No KYC required for virtual currency buy-in
- Prizes paid in virtual currency — player can convert to USD later (conversion requires KYC)
- This lets studios offer tournaments globally, with cash-out only in eligible regions
Geo-Fencing & Legal Framework
- Skill-based tournaments are legal in approximately 40 US states (Skillz operates under this model).
- Key legal test: skill must be the "dominant factor" in determining outcomes (not chance).
- States like AR, CT, DE, LA, SD have specific restrictions on cash skill gaming. Other states (AZ, MT, SC, TN) may have additional restrictions depending on game mechanics.
- ZBD handles geo-fencing (GPS + IP) to restrict cash tournaments to eligible jurisdictions.
- Players must be 18+ for cash tournaments (age verified via KYC).
Webhooks
tournament.created— Tournament createdtournament.entry.confirmed— Player successfully enteredtournament.entry.rejected— Entry rejected (ineligible region, insufficient funds, etc.)tournament.started— Tournament now activetournament.results.submitted— Results received, processing prizestournament.settled— All prizes distributedtournament.canceled— Tournament canceled, refunds issued
Error Codes
| Code | Description | Retryable |
|---|---|---|
tournament_full | Max players reached | No |
tournament_not_open | Tournament not accepting entries | No |
insufficient_wallet_balance | Player can't cover entry fee | Yes (after wallet load) |
region_restricted | Player's location not eligible for cash tournaments | No (offer virtual currency instead) |
age_verification_required | Player hasn't verified age 18+ | No (complete KYC) |
already_entered | Player already in this tournament | No |
min_players_not_met | Tournament canceled — not enough entries | No (auto-refund) |
results_mismatch | Submitted rankings don't match entry list | No (resubmit) |
How Tournaments Use All Three Workstreams
Compliance Architecture
Data boundaries, KYC flows, and regulatory framework
Data Boundary Architecture
Developer's Domain
Zero PII, zero PCI scope
- Tokenized userId (
usr_xxx) - Tokenized bankAccountId
- Masked last4 digits
- Status webhooks
- KYC tier (1/2/3)
- Balance amounts
Developer NEVER receives:
- Real names
- SSN / government IDs
- Bank account numbers
- Address data
- Identity documents
ZBD's Domain
Fully licensed, compliant
- Full legal name
- SSN / TIN
- Government ID documents
- Bank account + routing numbers
- Address verification
- Sanctions screening (OFAC)
- Transaction monitoring
- SAR filing
- 1099-NEC tax reporting (>$2K)
- ACH origination
- Plaid integration
- PCI-compliant vault
KYC Verification Sequence
Payout Compliance Flow
Sweepstakes Distinction (AB 831)
California AB 831 (effective Jan 1, 2026) bans sweepstakes casinos that use dual-currency models. Connecticut, New Jersey, Nevada, and Montana have passed similar laws.
| Factor | Sweepstakes Casinos (banned) | ZBD Virtual Currency (legal) |
|---|---|---|
| How players earn | Purchase gold coins, receive sweeps coins as "bonus" | Earn through gameplay skill/performance |
| Outcome determination | Chance-based (slots, roulette) | Skill-based (game performance, tournament ranking) |
| Virtual currency source | Purchased or received as promotional bonus | Earned through measurable gameplay actions |
| Cash-out mechanism | Sweeps coins to cash (disguised gambling) | Earned coins to USD via licensed money transmitter |
| Regulatory classification | Illegal gambling (AB 831) | Licensed money transmission (FinCEN MSB + state MTLs) |
API-level protection: The ledger API enforces that virtual currency can only be credited via POST /v1/ledger/credit (called by the developer's server, not purchased by the player). There is no endpoint for players to directly buy virtual currency with cash and then convert it back — that would be a sweepstakes model.
PII Edge Cases & Mitigations
| Edge Case | Risk | Mitigation |
|---|---|---|
| Customer support | Player contacts the game studio about a failed payout — studio may need info to troubleshoot | Support handoff flow: POST /v1/support/tickets creates a ZBD support case linked to a userId. Player is redirected to ZBD's support portal. Developer never handles the PII. |
| Chargeback disputes | Card network sends dispute evidence requests that may contain buyer PII | Dispute proxy: ZBD acts as the Merchant of Record for checkout transactions. Developer receives only checkout.dispute.created webhook with dispute ID and amount — no PII. |
| Tax reporting for developer-funded prizes | Who is the "payor" for 1099-NEC purposes when the developer funds tournament prizes? | ZBD as payor of record: ZBD is the entity transmitting money to players, making ZBD responsible for tax reporting. Same model Skillz uses. |
| Player communications | Developer wants to email players about payouts — needs at minimum an email address | Scoped consent: POST /v1/users accepts email as an optional field the developer provides at registration. This is data the developer already has from their own signup flow. |
How We Communicate Compliance to Developers
- "Zero PII" badge on every relevant API page — visual indicator that the endpoint keeps developers out of PCI/PII scope.
- "Honest edge cases" section — proactively list edge cases with mitigations. Builds trust vs. making claims that fall apart under scrutiny.
- Compliance checklist in integration guide:
- You handle: game logic, reward rules, player UX
- ZBD handles: identity verification, bank data, sanctions, tax reporting, dispute resolution
- You never need: PCI certification, OFAC screening, 1099 filing, money transmitter license
- "What you don't build" section — explicitly list everything ZBD handles so developers feel confident.
- Sandbox with compliance simulation — test KYC flows, trigger failure scenarios, simulate returns, test dispute handling.
Player Journeys
The same platform from the player's perspective
Journey 1: Casual Player Earns a Reward
- 1. Player wins a match — In-game notification: "You earned 500 Gold Coins!" No signup, no KYC — Tier 0, coins credited instantly via ledger API.
- 2. Player accumulates 2,000 Gold Coins — In-game prompt: "Cash out your coins? Convert to $20.00." Player taps "Cash Out."
- 3. First cash-out triggers KYC — Game opens ZBD's hosted verification page (Tier 1: name, DOB, country). Takes under 2 minutes. No SSN, no ID upload for amounts under $200/month. Player redirected back to game.
- 4. Player links bank account — ZBD's hosted Plaid flow opens. Player selects their bank, logs in. Game never sees bank details. Player sees "Chase ****6789 linked."
- 5. Player receives payout — Conversion: 2,000 Gold Coins to $20.00 to ZBD wallet to ACH to bank. Arrives in 1-2 business days (instant via Lightning if player uses ZBD wallet).
Journey 2: Competitive Player Enters a Tournament
- 1. Player sees "Friday Night Showdown" — $5 entry, $225 first prize. Player taps "Enter Tournament."
- 2. Entry fee flow — If wallet has $5+: instant debit, entry confirmed. If wallet is empty: "Load your wallet" via Plaid bank link or ZBD checkout (card). Entry fee held in escrow.
- 3. Geo + age check (invisible to player) — ZBD checks GPS + IP (player is in Texas — skill gaming legal). KYC confirms player is 22. If player were in Arkansas: "Cash tournaments aren't available in your area. Try a Gold Coin tournament instead!"
- 4. Player competes and wins — Game submits results via API. Player sees: "1st Place! $225 prize credited to your wallet."
- 5. Player cashes out winnings — $225 credited to ZBD wallet (from escrow). Player taps "Withdraw to bank" — ACH payout initiated. Arrives in 1-2 business days.
Journey 3: Player Buys Items via Web Store
- 1. Player sees in-game banner — "20% off Gold Coin packs — buy direct!" Link opens game's web store (not app store).
- 2. Checkout — ZBD's hosted checkout page loads with Apple Pay / card options. Player pays $7.99 for 1,000 Gold Coins (vs. $9.99 through App Store IAP). Studio saves on app store fees, passes some savings to player.
- 3. Coins credited — Webhook fires, game credits 1,000 Gold Coins instantly. Player returns to game with coins ready to spend.
Why the Player Perspective Matters for API Design
| Player Need | API Design Implication |
|---|---|
| Instant feedback ("I won!") | Ledger credit is synchronous, returns balanceAfter immediately |
| Minimal friction to earn | Tier 0 KYC (email only) for earning — no barriers to engagement |
| Friction only at cash-out | KYC triggered by conversion/payout, not by earning |
| Trust that money is safe | Escrow model for tournaments — entry fees protected from studio manipulation |
| Know when money arrives | Webhook-driven push notifications with estimatedArrival on payout object |
| Fallback when restricted | Virtual currency tournaments available globally, cash only where legal |
| Transparent fees | Payout object includes fee field so players see exactly what they receive |
Roadmap
6-month phased rollout, each phase building on the infrastructure of the last
Ship:
- Users & Identity API (registration, KYC initiation)
- Bank Account Linking API (via hosted flow)
- Payout API (create, retrieve, list, cancel)
- Webhook infrastructure
- Developer portal: docs, API keys, sandbox
- Hosted KYC widget (Tier 1 + Tier 2)
Why first: Revenue-generating primitive. Studios can start paying players immediately. Proves the compliance architecture works. Generates the integration patterns that Wallet + Currency build on.
Ship:
- Wallet balance + transaction APIs
- Wallet load from linked bank account
- ZBD Checkout Widget (hosted card payment for web stores)
- Wallet load webhooks + checkout webhooks
Why second: Reuses 80% of Phase 1 infrastructure (banking rails, bank linking, KYC). Adding checkout completes the bi-directional money flow — money in (checkout + wallet load) AND money out (payouts). This is what makes ZBD a full payment platform, not just a payout tool.
Ship:
- Currency configuration API
- Ledger APIs (credit, debit, transfer, balance)
- Conversion API (virtual to wallet)
- Tournament APIs (create, enter, results, settle)
- Escrow system for tournament buy-ins
- Geo-fencing for cash tournament eligibility
- Anti-fraud rules (velocity checks, pattern detection)
Why last: Highest compliance complexity — tournaments layer skill-gaming regulations on top of money transmission. But by this point, all the underlying infrastructure exists. Tournaments are the killer demo: they exercise every API in one use case.
Phase Gates
| Gate | Phase 1 to 2 | Phase 2 to 3 |
|---|---|---|
| Technical | Webhook infra handles 10K+ events/day | Ledger + escrow can process 100K+ txns/day |
| Compliance | KYC pass rate >70% | Skill-gaming legal review complete for target states; conversion rate model approved |
| Business | 3+ studios live with payouts | Wallet loading + checkout volume validates demand; 1+ studio committed to tournament pilot |
| DX | Time-to-first-payout under 2hrs in sandbox | Bank linking + checkout completion rate >60% |
Execution Risks
| Risk | Impact | Mitigation |
|---|---|---|
| Banking partner compliance review delays Phase 1 | Entire roadmap shifts right | Start banking partner conversations before engineering begins. Have backup partners identified. |
| KYC drop-off exceeds benchmarks | Low payout adoption | Tier 0 (no KYC) lets players engage immediately. Monitor conversion funnel weekly. |
| Skill gaming legal review blocks cash tournaments | Limited geo coverage | Virtual currency tournaments launch everywhere. Cash tournaments roll out state-by-state. |
| Stripe/Xsolla announces competing product | Competitive pressure | Accelerate micropayment support and tournament escrow — features they'd need 12+ months to build. |
Fallback Plan
If timeline slips, Phase 1 (payouts) is the non-negotiable MVP. If we're behind, we ship payouts + wallet loading in months 1-4 and push virtual currency + tournaments to months 5-9, extending to a 9-month roadmap. The brief says payout-to-bank is the primary focus — we protect that.
Success Metrics
Measuring DX quality, integration adoption, and commercial outcomes
DX Quality
| Metric | Target | How to Measure |
|---|---|---|
| Time to first API call | <15 min | Sandbox analytics |
| Time to first payout (sandbox) | <1 hour | Funnel tracking |
| API p99 latency | <500ms | APM |
| API error rate (non-client errors) | <0.1% | Monitoring |
| Doc page completion rate | >80% per guide | Analytics |
| Support tickets per integration | <5 during onboarding | Zendesk |
Integration Adoption
| Metric | Target (6mo) | How to Measure |
|---|---|---|
| Studios with API keys | 50+ | Dashboard |
| Studios live in production | 15+ | First production payout |
| Integration completion rate | >40% (signup to live) | Funnel |
| Monthly active integrations | 12+ | API call volume |
| SDK adoption rate | >60% of integrations use SDK | SDK vs raw API calls |
Commercial Outcomes
| Metric | Target (6mo) | How to Measure |
|---|---|---|
| Total payout volume (GMV) | $2M+ monthly | Ledger |
| Payout success rate | >95% | Payout status tracking |
| Revenue per integration | $5K+ monthly avg | Billing |
| Net revenue retention | >120% | Cohort analysis |
| Time-to-live | <14 days (signup to first prod payout) | Funnel |