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

Workstream 1

Payout to Bank

Studios trigger bank payouts on behalf of players. ZBD handles banking rails, compliance screening, and settlement. ACH, instant via Lightning.

Workstream 2

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.

Workstream 3

Virtual Currency + Tournaments

Convertible virtual currency ledger with cash-out support. Skill-based tournaments with escrow, geo-fencing, and automated prize distribution.

Competitive Landscape

CompetitorPay-InPay-OutGaming-NativeMicropaymentsLicensing
Xsolla700+ methodsNoYesNoMoR model
TipaltiNo196 countriesNoNoVia partners
StripeYesYesNoNo ($0.50 min)Yes
CoinflowYes (crypto+fiat)YesPartialYes (crypto)Growing ($25M Series A)
NuveiYesYesiGaming onlyNoYes (gambling)
SkillzTournaments onlyTournaments onlyYesNoSkill gaming
ZBDYes (checkout)Yes (payouts)YesYes ($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
Scoped API Keys

Keys can be restricted to specific permissions: payout:read, payout:write, user:read, ledger:write. Production keys should use minimum required scopes.

Key Rotation

POST /v1/api-keys/rotate enables zero-downtime rotation. The old key remains valid for 24 hours after rotation.

IP Allowlisting

Production keys support IP allowlisting to restrict API access to known server addresses.

Webhook Signatures

X-ZBD-Signature header with HMAC-SHA256 of the raw request body using your webhook secret.

Idempotency

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.99 always fails, $1.00 always succeeds

Project Management

MethodEndpointDescription
GET/v1/project/balanceGet your project's current balance (available funds for payouts + prize pools).
GET/v1/project/transactionsProject-level transaction history (top-ups, payouts, fees, escrow holds).

Webhook Management

MethodEndpointDescription
POST/v1/webhooksRegister a webhook endpoint URL. Specify which events to subscribe to.
GET/v1/webhooksList 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}/testSend 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

At-least-once delivery — Events may be delivered more than once. Use the eventId field to deduplicate.
Retry schedule — If your endpoint returns a non-2xx status, ZBD retries with exponential backoff: 5s, 25s, 125s, 625s, 3125s (max 5 retries over ~1 hour).
No ordering guarantee — Events may arrive out of order. Use timestamp and object status to determine current state.
Event replay GET /v1/events returns a log of all events. Filter by type, createdAfter, createdBefore. Use this to replay missed events during outages.
Event TTL — Events are retained for 30 days.

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 fxRate and fxFee fields
  • 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 documents

Endpoints

MethodEndpointDescription
POST/v1/usersRegister a player. Accepts externalId, email, country. Returns a ZBD userId.
GET/v1/users/{userId}Get user profile (KYC status, tier, linked accounts).
GET/v1/usersList users. Filter by kycStatus, country, createdAfter.
POST/v1/users/{userId}/verifyInitiate KYC verification. Returns a verificationUrl (hosted by ZBD).
GET/v1/users/{userId}/verify/statusCheck current KYC verification status.

KYC Tiers

Tiered verification minimizes friction and maximizes conversion. Players earn first, verify only at cash-out.

TierRequirementsLimitsUse Case
Tier 0Email + phone onlyEarn virtual currency, no cash-outIn-game economy participation
Tier 1+ Name, DOB, countryPayouts up to $200/monthCasual player rewards
Tier 2+ Government ID, addressPayouts up to $2,000/monthRegular earners
Tier 3+ SSN/TIN, enhanced due diligenceUnlimited (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 registered
  • user.kyc.submitted — Player started verification
  • user.kyc.approved — Verification passed (includes kycTier)
  • user.kyc.rejected — Verification failed (includes reason)
  • 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 numbers

Bank Account Linking

Before a payout can be created, the player must link a bank account through ZBD's hosted Plaid flow.

MethodEndpointDescription
POST/v1/users/{userId}/bank-accountsCreate a bank account linking session. Returns linkUrl (ZBD-hosted, powered by Plaid).
GET/v1/users/{userId}/bank-accountsList 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

MethodEndpointDescription
POST/v1/payoutsCreate a payout. Specify userId, bankAccountId, amount (cents), currency.
GET/v1/payouts/{payoutId}Retrieve payout details and current status.
GET/v1/payoutsList payouts. Filter by userId, status, date range. Cursor-paginated.
POST/v1/payouts/{payoutId}/cancelCancel a payout (only if status is pending).
POST/v1/payouts/batchCreate 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

stateDiagram-v2 [*] --> pending pending --> processing pending --> canceled : Developer cancels processing --> in_transit processing --> failed in_transit --> completed completed --> returned : Bank rejects after deposit

Failure Codes

CodeDescriptionRetryable
insufficient_project_balanceProject doesn't have enough funds to cover payoutYes (after top-up)
kyc_requiredUser hasn't completed required KYC tierNo (redirect user to verify)
kyc_pendingKYC submitted but not yet approvedYes (after approval)
kyc_rejectedKYC verification failedNo
payout_limit_exceededExceeds user's tier limitNo (upgrade tier)
bank_account_not_verifiedBank account linking not completeNo
invalid_bank_accountBank account details invalidNo (re-link)
bank_account_closedDestination account is closedNo (re-link)
account_frozenZBD flagged account for reviewNo (contact support)
sanctions_matchUser flagged by sanctions screeningNo
duplicate_payoutIdempotency key already usedNo
amount_too_smallBelow minimum payout ($1.00)No
amount_too_largeExceeds single-payout maximumNo (split into multiple)
velocity_limitToo many payouts in time windowYes (after cooldown)
internal_errorZBD system errorYes (with backoff)

Webhooks

  • payout.pending — Payout created, awaiting processing
  • payout.processing — Submitted to banking partner
  • payout.in_transit — Funds left ZBD, in transit to bank
  • payout.completed — Funds deposited successfully
  • payout.failed — Payout failed (includes failureCode + failureMessage)
  • payout.canceled — Payout canceled by developer
  • payout.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 perimeter

Endpoints

MethodEndpointDescription
GET/v1/users/{userId}/walletGet wallet balance and status.
POST/v1/users/{userId}/wallet/loadInitiate a wallet load from a linked bank account.
GET/v1/users/{userId}/wallet/loads/{loadId}Get load status.
GET/v1/users/{userId}/wallet/loadsList wallet loads. Filter by status, date range.
GET/v1/users/{userId}/wallet/transactionsWallet 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

stateDiagram-v2 [*] --> pending pending --> processing processing --> completed processing --> failed

SDK vs. Developer Responsibility

SDK Handles (inside ZBD)Developer Receives
Bank auth via Plaid LinkTokenized bankAccountId
ACH debit initiationLoad status + webhooks
Reg E compliance (error resolution)load.failed with reason code
Fraud screening on loadApproved/rejected decision
Balance updatesUpdated wallet balance via API

Webhooks

  • wallet.load.pending — Load initiated
  • wallet.load.completed — Funds added to wallet
  • wallet.load.failed — Load failed (includes reason)
  • wallet.balance.updated — Balance changed (any reason)

Error Codes

CodeDescriptionRetryable
bank_account_not_linkedNo verified bank accountNo (link first)
insufficient_bank_fundsPlayer's bank account has insufficient fundsYes (after player adds funds)
load_limit_exceededDaily/monthly load limit reachedYes (after limit resets)
kyc_requiredKYC tier insufficient for wallet loadingNo (upgrade tier)
wallet_suspendedWallet suspended for reviewNo
duplicate_loadIdempotency collisionNo

Checkout (Web Store)

Hosted checkout widget for direct-to-consumer purchases, bypassing app store fees

Endpoints

MethodEndpointDescription
POST/v1/checkout/sessionsCreate a checkout session. Returns a checkoutUrl.
GET/v1/checkout/sessions/{sessionId}Get session status and payment details.
GET/v1/checkout/sessionsList checkout sessions. Filter by userId, status, date range.
POST/v1/checkout/sessions/{sessionId}/refundRefund 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. 1. Create session — Developer creates checkout session via API, receives a checkoutUrl.
  2. 2. Redirect player — Player is redirected to ZBD's hosted checkout page (card entry, Apple Pay, Google Pay).
  3. 3. Process payment — ZBD processes payment (PCI-compliant, fraud screening, 3D Secure).
  4. 4. Fulfill order — Webhook checkout.completed fires. 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 player
  • checkout.failed — Payment failed
  • checkout.refunded — Refund processed
  • checkout.dispute.created — Chargeback initiated
  • checkout.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

MethodEndpointDescription
POST/v1/currenciesDefine 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}/rateGet 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

MethodEndpointDescription
POST/v1/ledger/creditCredit virtual currency to a user (player earned coins).
POST/v1/ledger/debitDebit virtual currency (player spent coins in-game).
POST/v1/ledger/transferPlayer-to-player transfer.
GET/v1/ledger/balance/{userId}Get player's virtual currency balance.
GET/v1/ledger/transactionsTransaction history. Filter by userId, type, date range.
POST/v1/conversionsConvert virtual currency to USD. Requires KYC. Credits user's ZBD wallet.
GET/v1/conversions/{conversionId}Get conversion status.
GET/v1/conversionsList 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 user
  • ledger.debited — Virtual currency debited from user
  • ledger.transfer.completed — P2P transfer completed
  • conversion.pending — Conversion requested
  • conversion.completed — Conversion processed, wallet credited
  • conversion.failed — Conversion failed (includes reason)

Error Codes

CodeDescriptionRetryable
insufficient_virtual_balanceUser doesn't have enough virtual currencyNo
conversion_limit_exceededDaily conversion limit reachedYes (after reset)
below_minimum_conversionAmount below minimum conversion thresholdNo
kyc_requiredKYC tier insufficient for conversionNo
currency_not_foundInvalid currency IDNo
reward_pool_depletedProject's reward pool is emptyYes (after top-up)
velocity_limitToo many ledger operations in time windowYes (after cooldown)

Tournaments

Skill-based tournaments with buy-in escrow, automated prize distribution, and geo-fencing

Endpoints

MethodEndpointDescription
POST/v1/tournamentsCreate a tournament with entry fee, prize structure, and eligibility rules.
GET/v1/tournaments/{tournamentId}Get tournament details and status.
GET/v1/tournamentsList tournaments. Filter by status, currencyType, date range.
POST/v1/tournaments/{tournamentId}/enterEnter a player. Debits entry fee from wallet (cash) or ledger (virtual).
POST/v1/tournaments/{tournamentId}/resultsSubmit final results/rankings. Triggers prize distribution.
POST/v1/tournaments/{tournamentId}/cancelCancel tournament. Refunds all entry fees.
GET/v1/tournaments/{tournamentId}/entriesList all entries/participants.
GET/v1/tournaments/{tournamentId}/payoutsList 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

stateDiagram-v2 [*] --> draft draft --> open : Accepting entries open --> active : In progress open --> canceled : Refunds issued active --> completed completed --> settled : Prizes paid

Enter Tournament Flow

POST /v1/tournaments/{tournamentId}/enter
{
  "userId": "usr_a1b2c3d4",
  "internalId": "entry_player123_trn_d4e5f6"
}

ZBD automatically:

  1. Checks player eligibility (KYC tier, age 18+, geo-location in eligible region)
  2. Debits entry fee from player's wallet
  3. Holds entry fee in escrow until tournament completes
  4. 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:

  1. Validates rankings (all entrants accounted for)
  2. Distributes prizes per prizeStructure — credits to winners' wallets
  3. Takes platform fee from prize pool (e.g., 10%)
  4. Fires tournament.settled webhook with full payout details

Prize Source Options

SourceHow it WorksBest For
entry_feesPrize pool = sum of entry fees minus platform fee. Skillz model.Competitive esports, PvP games
developer_fundedStudio pre-funds the prize pool from project balance. Entry fee can be $0.Promotional events, player acquisition
hybridBase 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 created
  • tournament.entry.confirmed — Player successfully entered
  • tournament.entry.rejected — Entry rejected (ineligible region, insufficient funds, etc.)
  • tournament.started — Tournament now active
  • tournament.results.submitted — Results received, processing prizes
  • tournament.settled — All prizes distributed
  • tournament.canceled — Tournament canceled, refunds issued

Error Codes

CodeDescriptionRetryable
tournament_fullMax players reachedNo
tournament_not_openTournament not accepting entriesNo
insufficient_wallet_balancePlayer can't cover entry feeYes (after wallet load)
region_restrictedPlayer's location not eligible for cash tournamentsNo (offer virtual currency instead)
age_verification_requiredPlayer hasn't verified age 18+No (complete KYC)
already_enteredPlayer already in this tournamentNo
min_players_not_metTournament canceled — not enough entriesNo (auto-refund)
results_mismatchSubmitted rankings don't match entry listNo (resubmit)

How Tournaments Use All Three Workstreams

flowchart LR A["Wallet Loading\n\nPlayer loads wallet\nfrom bank account"] --> B["Tournament Escrow\n\nEntry fee debited\nfrom wallet, held\nin escrow"] B --> C["Payout to Bank\n\nWinner cashes out\nwinnings from wallet\nto bank"] A --> D["Virtual Currency\n\nAlternative:\ntournament in virtual\ncurrency, convert\nto cash later"] D --> C

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

sequenceDiagram participant Dev as Developer Server participant API as ZBD API participant UI as ZBD Hosted UI participant KYC as KYC Provider Dev->>API: POST /users/{id}/verify API-->>Dev: { verificationUrl } Dev->>UI: Redirect player UI->>KYC: Player submits ID + selfie KYC-->>UI: Verification result API-->>Dev: Webhook: user.kyc.approved (tier: 2) Note over Dev: Developer can now trigger payouts up to Tier 2 limits

Payout Compliance Flow

sequenceDiagram participant Dev as Developer Server participant ZBD as ZBD (Internal) participant Bank as Banking Partner Dev->>ZBD: POST /v1/payouts Note over ZBD: 1. Validate KYC tier Note over ZBD: 2. Check sanctions (OFAC/SDN) Note over ZBD: 3. Transaction monitoring Note over ZBD: 4. Velocity checks Note over ZBD: 5. Debit project balance Note over ZBD: If amount >$2,000/yr for user: flag for 1099-NEC ZBD->>Bank: Initiate ACH transfer ZBD-->>Dev: Webhook: payout.processing Bank-->>ZBD: ACH settled ZBD-->>Dev: Webhook: payout.completed

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.

FactorSweepstakes Casinos (banned)ZBD Virtual Currency (legal)
How players earnPurchase gold coins, receive sweeps coins as "bonus"Earn through gameplay skill/performance
Outcome determinationChance-based (slots, roulette)Skill-based (game performance, tournament ranking)
Virtual currency sourcePurchased or received as promotional bonusEarned through measurable gameplay actions
Cash-out mechanismSweeps coins to cash (disguised gambling)Earned coins to USD via licensed money transmitter
Regulatory classificationIllegal 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 CaseRiskMitigation
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

  1. "Zero PII" badge on every relevant API page — visual indicator that the endpoint keeps developers out of PCI/PII scope.
  2. "Honest edge cases" section — proactively list edge cases with mitigations. Builds trust vs. making claims that fall apart under scrutiny.
  3. 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
  4. "What you don't build" section — explicitly list everything ZBD handles so developers feel confident.
  5. 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. 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. 2. Player accumulates 2,000 Gold Coins — In-game prompt: "Cash out your coins? Convert to $20.00." Player taps "Cash Out."
  3. 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. 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. 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. 1. Player sees "Friday Night Showdown" — $5 entry, $225 first prize. Player taps "Enter Tournament."
  2. 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. 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. 4. Player competes and wins — Game submits results via API. Player sees: "1st Place! $225 prize credited to your wallet."
  5. 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. 1. Player sees in-game banner — "20% off Gold Coin packs — buy direct!" Link opens game's web store (not app store).
  2. 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. 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 NeedAPI Design Implication
Instant feedback ("I won!")Ledger credit is synchronous, returns balanceAfter immediately
Minimal friction to earnTier 0 KYC (email only) for earning — no barriers to engagement
Friction only at cash-outKYC triggered by conversion/payout, not by earning
Trust that money is safeEscrow model for tournaments — entry fees protected from studio manipulation
Know when money arrivesWebhook-driven push notifications with estimatedArrival on payout object
Fallback when restrictedVirtual currency tournaments available globally, cash only where legal
Transparent feesPayout 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

Phase 1 Payout to Bank Months 1-2

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.

Phase 2 Wallet Loading + Checkout Months 3-4

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.

Phase 3 Virtual Currency + Tournaments Months 5-6

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

GatePhase 1 to 2Phase 2 to 3
TechnicalWebhook infra handles 10K+ events/dayLedger + escrow can process 100K+ txns/day
ComplianceKYC pass rate >70%Skill-gaming legal review complete for target states; conversion rate model approved
Business3+ studios live with payoutsWallet loading + checkout volume validates demand; 1+ studio committed to tournament pilot
DXTime-to-first-payout under 2hrs in sandboxBank linking + checkout completion rate >60%

Execution Risks

RiskImpactMitigation
Banking partner compliance review delays Phase 1Entire roadmap shifts rightStart banking partner conversations before engineering begins. Have backup partners identified.
KYC drop-off exceeds benchmarksLow payout adoptionTier 0 (no KYC) lets players engage immediately. Monitor conversion funnel weekly.
Skill gaming legal review blocks cash tournamentsLimited geo coverageVirtual currency tournaments launch everywhere. Cash tournaments roll out state-by-state.
Stripe/Xsolla announces competing productCompetitive pressureAccelerate 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

MetricTargetHow to Measure
Time to first API call<15 minSandbox analytics
Time to first payout (sandbox)<1 hourFunnel tracking
API p99 latency<500msAPM
API error rate (non-client errors)<0.1%Monitoring
Doc page completion rate>80% per guideAnalytics
Support tickets per integration<5 during onboardingZendesk

Integration Adoption

MetricTarget (6mo)How to Measure
Studios with API keys50+Dashboard
Studios live in production15+First production payout
Integration completion rate>40% (signup to live)Funnel
Monthly active integrations12+API call volume
SDK adoption rate>60% of integrations use SDKSDK vs raw API calls

Commercial Outcomes

MetricTarget (6mo)How to Measure
Total payout volume (GMV)$2M+ monthlyLedger
Payout success rate>95%Payout status tracking
Revenue per integration$5K+ monthly avgBilling
Net revenue retention>120%Cohort analysis
Time-to-live<14 days (signup to first prod payout)Funnel