X-Mock-Lifecycle and X-Mock-Outcome) that let you drive any response shape on demand.| Area | Sandbox behaviour |
|---|---|
| Money movement | None. No funds move on any rail or chain. |
| State | The sandbox does not advance a transfer through the lifecycle on its own. Use the testing headers below to drive any state you want to test. |
| Reference data | A fixed catalogue (this page). Values outside it are rejected with the same error shapes you will see later. |
| Tracking IDs | Always carry forward the exact cbtx_tracking_id the quote returned into every downstream call (confirm, deposit, payout, status). A random or hand-written UUID returns 404. |
| Quote expiry | Read expires_at from the quote response. Once it passes, request a new quote. |
| Webhooks | No deliveries are sent. The management, event, and delivery-attempt endpoints do answer, against a fixed corpus, so you can exercise search, replay, and the subscription lifecycle. Test your handler against the contract in the Webhooks Guide. |
| Response variability | Some success fields (rates, addresses, timestamps, ids) vary between calls. Use the testing headers below to pin a deterministic outcome. |
401 { "message": "Unauthorized" }.| Parameter | Value |
|---|---|
client_id | crossfx-sandbox-client |
client_secret | sandbox-mock-secret |
grant_type set to client_credentials, then use the access_token it returns as a bearer token on every other call. Any other client id returns 401 with invalid_client, and a wrong secret returns 401 with unauthorized_client.country_code is the ISO-3166 alpha-2 code and is case-insensitive on input.| Country | country_code | Dial prefix | Destination currency |
|---|---|---|---|
| Kenya | KE | +254 | KES |
| Ghana | GH | +233 | GHS |
| Nigeria | NG | +234 | NGN |
404 NOT_FOUND.| Role | Currencies |
|---|---|
| Source, crypto | USDC, USDT |
| Source, fiat | USD |
| Destination, fiat payout | KES, GHS, NGN |
| Recognised by Fetch FX Rates | USD, USDC, USDT, KES, GHS, NGN, EUR, JPY |
404 NOT_FOUND. On Fetch FX Rates the base parameter is matched case-sensitively, so usd is a 404 while USD resolves.| Field | Allowed values |
|---|---|
chain | POLYGON, TRON, ETHEREUM, SOLANA |
currency_code | USDC, USDT |
chain is matched case-sensitively. An unrecognised chain returns 422 VALIDATION_FAILED with Must be one of: POLYGON, TRON, ETHEREUM, SOLANA., and an unrecognised currency_code returns 404 NOT_FOUND.country_code. branch_details_required is true when the bank has at least one branch in the catalogue.| Country | Bank | bank_code | Has branches |
|---|---|---|---|
KE | Equity Bank | cbtx_ke_equity_bank | Yes |
KE | KCB Bank | cbtx_ke_kcb_bank | Yes |
KE | Co-operative Bank | cbtx_ke_cooperative_bank | No |
GH | GCB Bank | cbtx_gh_gcb_bank | Yes |
GH | Ecobank Ghana | cbtx_gh_ecobank_ghana | No |
GH | Absa Bank Ghana | cbtx_gh_absa_bank_ghana | Yes |
NG | Access Bank | cbtx_ng_access_bank | Yes |
NG | Zenith Bank | cbtx_ng_zenith_bank | Yes |
NG | Guaranty Trust Bank | cbtx_ng_guaranty_trust_bank | No |
bank_code outside this catalogue returns 404 NOT_FOUND. country_code is matched case-sensitively on the banks lookup, so ke returns 404 while KE resolves.<bank_code>_<NNN>, where NNN is the zero-padded branch number. The List Bank Branches endpoint returns exactly the branches below, and a bank with no branches returns an empty list.| Bank | bank_code | Branch numbers | Example branch code |
|---|---|---|---|
| Equity Bank | cbtx_ke_equity_bank | 1, 5, 12 | cbtx_ke_equity_bank_001 |
| KCB Bank | cbtx_ke_kcb_bank | 7 | cbtx_ke_kcb_bank_007 |
| Co-operative Bank | cbtx_ke_cooperative_bank | (none) | |
| GCB Bank | cbtx_gh_gcb_bank | 2, 9 | cbtx_gh_gcb_bank_002 |
| Ecobank Ghana | cbtx_gh_ecobank_ghana | (none) | |
| Absa Bank Ghana | cbtx_gh_absa_bank_ghana | 4 | cbtx_gh_absa_bank_ghana_004 |
| Access Bank | cbtx_ng_access_bank | 3, 6, 10, 11 | cbtx_ng_access_bank_003 |
| Zenith Bank | cbtx_ng_zenith_bank | 8 | cbtx_ng_zenith_bank_008 |
| Guaranty Trust Bank | cbtx_ng_guaranty_trust_bank | (none) |
BANK beneficiary, a supplied branch_code must be one of the branch codes above for that bank. A bank with no branches rejects any supplied branch code with 404 NOT_FOUND.account_details.phone_number is required on every beneficiary and is validated against a strict format.| Rule | Detail |
|---|---|
| Format | + followed by a 3-digit country dial code and exactly 9 subscriber digits, 12 digits in total |
| Valid examples | +254712345678 (KE), +233241234567 (GH), +234812345678 (NG) |
| Invalid examples | 0712345678 (no + or country code), +254 712 345 678 (spaces), +2547123456 (too short) |
country_code: KE → +254, GH → +233, NG → +234.X-Mock-LifecycleX-Mock-Lifecycle shapes a successful response into a specific point in the transfer lifecycle. It lets you request the exact transfer state you want to test, so you can drive your status-handling code through every stage deterministically.status or status/variant pair, slash-delimited.| Endpoint | What the token shapes |
|---|---|
| Get Transfer Status | Transfer status plus the nested deposit / payout / refund blocks. A block is present only when the token includes that leg. |
| Confirm Transfer | Transfer status, the link-ids deposit_id / payout_id / refund_id (set when that leg exists), and completed_at |
| Initiate Deposit | The deposit status only. received_at and failed_at follow the status. |
| Initiate Payout | The payout status only. paid_at and failed_at follow the status. |
pending_deposit, pending_payout, pending_reconcile, completed, failed, refunding, refunded, refund_failed, cancelled. A deposit, payout, or refund leg is one of pending, processing, succeeded, failed.X-Mock-Lifecycle, because they render no transfer state: Get Access Token, Generate Quote, Create Beneficiary, Get Beneficiary, List Banks, List Bank Branches, Fetch FX Rates, List Supported Corridors, List Crypto Deposit Addresses, and every webhook operation.X-Mock-OutcomeX-Mock-Lifecycle shapes a success, X-Mock-Outcome forces a specific error or terminal outcome, so you can exercise your failure handling on demand.code it produces.| Endpoint | What the tokens force |
|---|---|
| Get Access Token | Credential and grant failures, plus a token that is already expired |
| Fetch FX Rates | A corridor with no active pricing configuration |
| Create Beneficiary | Screening outcomes |
| Generate Quote | Beneficiary resolution, pricing refusals, and amount-band limits |
| Confirm Transfer | Quote expiry, sender screening, ownership, and race conflicts |
| List Crypto Deposit Addresses | Upstream address-provider failures |
| Initiate Deposit | A transfer in the wrong state, and an internal error |
| Initiate Payout | A transfer in the wrong state, no available provider, and a payout accepted then failed |
| Create Webhook Subscription | The active-subscription limit, and an endpoint URL conflict |
| Update Webhook Subscription | An endpoint URL conflict |
401 before anything else.X-Mock-Outcome: if set to a recognised token, the forced outcome wins.422, 404, and 400 responses.X-Mock-Lifecycle: shapes the success body, once the request has organically earned a success.error types that ship under VALIDATION_FAILED.Missing data for required field., Field may not be null., Not a valid string., Not a valid number., Not a valid email address., Date must be in format %Y-%m-%d, and Must be one of: <values>.. The sandbox produces no 403 and no 429, so those two rows cannot be rehearsed against it.