# Banxa Developer Documentation — Full Content > Complete content from all Banxa integration documentation. For a structured index with descriptions, see llms.txt. --- Source: https://docs.banxa.com/AI-OVERVIEW.md # Banxa Developer Documentation — AI Overview This file provides orientation across all three Banxa integration products. Read this first, then consult the product-specific AI-METADATA.md for the product you are working with. --- ## What Banxa provides Banxa is a fiat-to-crypto and crypto-to-fiat ramp infrastructure provider. Partners embed Banxa into their products so their users can buy and sell cryptocurrency without the partner needing to build compliance, KYC, payment processing, or settlement infrastructure. Banxa handles: KYC and AML compliance, payment rails (cards, bank transfers, Apple Pay, Google Pay, local methods), crypto settlement, and regulatory licensing across 150+ countries. --- ## Primary product decision ``` Does the partner perform their own KYC? │ ├─→ YES — partner has KYC infrastructure, a backend, and a mobile app │ → Banxa Native API │ Headless. No Banxa screens, no redirects. Partner orchestrates │ identity, eligibility, and ramp creation via server-to-server API. │ SDK required for card/Apple Pay/Google Pay (PCI). Bank transfers: API only. │ └─→ NO — partner wants Banxa to handle KYC and payments → Banxa Hosted Checkout Banxa hosts the checkout experience. Three integration paths: Referral (URL), API (server-to-server), React Native SDK. No backend required for core flows. ``` **Legacy API** — the previous version of the Banxa API. Not recommended for new integrations. Existing partners should migrate to Banxa Hosted Checkout (direct successor) or Banxa Native API. --- ## Product comparison | | Banxa Native API | Banxa Hosted Checkout | Legacy API | |---|---|---|---| | Who hosts KYC | Partner | Banxa | Banxa | | Who hosts checkout UI | Partner | Banxa | Banxa | | Auth | HMAC server-to-server | x-api-key (HMAC for KYC sharing only) | HMAC | | User identifier | `identityReference` | `externalCustomerId` | `externalCustomerId` | | Backend required | Yes | No (except KYC sharing) | No | | Mobile SDK | React Native (cards/AP/GP) | React Native (full checkout) | None | | Integration paths | One (headless API) | Three (Referral, API, SDK) | Redirect/iFrame | | Current status | v0-beta | Live | Maintained, no new features | --- ## Environments All three products share the same environments: | Environment | Base URL | |---|---| | Sandbox | `https://api.banxa-sandbox.com` | | Production | `https://api.banxa.com` | Use sandbox for all development and testing. Sandbox credentials are separate from production credentials — retrieve them from the Partner Dashboard. Config changes in the Partner Dashboard (webhooks, supported assets, UI settings) take approximately 15 minutes to propagate. --- ## Shared concepts ### On-ramp and off-ramp - **On-ramp**: customer pays fiat, receives crypto (buy) - **Off-ramp**: customer sends crypto, receives fiat (sell) ### Webhooks All three products use webhooks for asynchronous event notifications (order/ramp status changes, identity updates). Webhooks are signed with HMAC-SHA256. Always return `200 OK` immediately and process asynchronously. Retries follow a Fibonacci sequence for up to 2 hours. **Webhook verification is the reverse of request signing.** Use your own webhook endpoint path in the canonical string — not a Banxa API path. ### Sandbox testing The sandbox mirrors production. No real funds are used. Configure a sandbox webhook endpoint separately from production. Sandbox credentials are issued separately and do not work in production. ### Partner Dashboard Self-serve portal for: API keys, webhook URLs, supported crypto and blockchains, UI settings, sandbox/production toggle. Changes propagate in ~15 minutes. --- ## HMAC authentication (shared pattern) Used by Banxa Native API for all calls, and by Banxa Hosted Checkout for KYC token sharing only. **Canonical string:** ``` METHOD\nPATH\nNONCE\nBODY ``` - `METHOD`: uppercase HTTP method - `PATH`: request path only — never the full URL with domain - `NONCE`: Unix timestamp in milliseconds — unique per request - `BODY`: compact JSON body (no whitespace); empty string for GET requests **Authorization header:** ``` Authorization: Bearer API_KEY:SIGNATURE:NONCE ``` Sign with HMAC-SHA256 using your API secret. Hex-encode the result. **Common errors:** - `40001`: nonce is not a valid Unix timestamp in milliseconds - `40002`: nonce is too old — system clock out of sync - `40003`: nonce already used — generate a new nonce per request - `40103`: signature mismatch — check path, newlines, compact JSON, correct secret --- ## Glossary | Term | Definition | |---|---| | On-ramp | Buying cryptocurrency with fiat currency | | Off-ramp | Selling cryptocurrency for fiat currency | | KYC | Know Your Customer — identity verification process | | AML | Anti-Money Laundering — transaction monitoring and compliance | | Fiat | Government-issued currency (USD, EUR, AUD, etc.) | | Crypto | Cryptocurrency (BTC, ETH, USDT, etc.) | | Blockchain | The specific network used for crypto delivery (e.g. ETH, TRON, SOL) | | Wallet address | The destination address for crypto delivery | | Memo / Tag | Additional identifier required by some blockchains (XRP, XLM, EOS, ATOM) | | Processing fee | Banxa's service fee for the transaction | | Network fee | Blockchain gas fee for the crypto transfer | | identityReference | Native API: partner's stable unique identifier per user | | externalCustomerId | Hosted Checkout: partner's stable unique identifier per user | | paymentReady | Native API eligibility signal: true means the transaction can proceed | | NONCE | Unix timestamp in milliseconds, unique per HMAC request | | Partner Dashboard | Self-serve configuration portal at dashboard.banxa.com | --- Source: https://docs.banxa.com/products/native-api/AI-METADATA.md # Banxa Native API — AI Metadata Structured reference for AI systems working with the Banxa Native API. Read AI-OVERVIEW.md first for cross-product context. **Current version:** v0-beta **Auth:** HMAC server-to-server **Base path:** `/eapi/v0/` --- ## Core Concept The Banxa Native API is a headless ramp infrastructure. The partner's app controls the entire UX — no Banxa screens, no redirects, no iFrames. The partner orchestrates: 1. Identity establishment (one of four models) 2. Eligibility check before every transaction 3. Ramp creation (pricing → ramp → payment) Card, Apple Pay, and Google Pay require the React Native SDK (`@banxa-official/react-native-sdk`) because card data cannot flow through the API without PCI compliance certification. The SDK handles this via Primer. Bank transfers are fully API-driven — no SDK needed. --- ## Key Entities **Identity** — represents a verified customer - Unique identifier: `identityReference` — partner-managed, stable per user, required on all calls - Must not contain PII. Format: alphanumeric and hyphens (e.g. `customer-12345`) - Identity records accumulate: verification done once is reused for future transactions - Established before ramp creation; re-used across transactions **Ramp** — a transaction converting between fiat and crypto - On-ramp: fiat → crypto (buy) - Off-ramp: crypto → fiat (sell) - Tracked by unique `id` - Created only after eligibility returns `paymentReady: true` **Quote** — real-time price for a transaction - Short-lived (~60 seconds) - Use `GET /eapi/v0/price` (instant) or `GET /eapi/v0/quote` (locked, returns `quoteId`) - Pass `quoteId` to ramp creation to guarantee the rate - Provide either `fiatAmount` or `cryptoAmount` — not both **Eligibility** — pre-transaction compliance gate - Call before every ramp creation - Returns `paymentReady: true/false` and `requirements[]` - Evaluated dynamically — context changes (amount, method, jurisdiction) may trigger new requirements --- ## Domain Model ``` Customer (identityReference) ├── Identity (one of four models) │ ├── kyc.status: PENDING → UNDER_REVIEW → VERIFIED / REJECTED │ └── account.blocked: boolean │ ├── Eligibility check (before each ramp) │ ├── paymentReady: true → proceed to ramp │ └── paymentReady: false + requirements[] → collect and resubmit │ └── Ramp ├── On-Ramp: source.fiat + target.crypto └── Off-Ramp: source.crypto + target.fiat ``` --- ## Integration Flow ``` 1. Establish identity (one-time per user, or upgrade as needed) │ 2. Check eligibility (before every transaction) │ ├─→ paymentReady: false → collect requirements → PATCH /identities → re-check │ └─→ paymentReady: true │ 3. Get price (GET /eapi/v0/price or GET /eapi/v0/quote) │ 4. Create ramp (POST /eapi/v0/ramps) │ ├─→ Bank transfer: display sourceDepositInstructions to customer └─→ Card / AP / GP: present native payment sheet via React Native SDK │ 5. Track via webhooks ``` --- ## Four Identity Models | Model | Endpoint | Who does KYC | Best for | |---|---|---|---| | Basic identity | `POST /eapi/v0/identities/basic` | None — Banxa uses risk tiers | Fast onboarding, low-value transactions | | KYC token sharing (Sumsub) | `POST /eapi/v0/identities/share/token` | Partner's KYC provider | Partners already using Sumsub | | Identity reliance | `POST /eapi/v0/identities/reliance` | Partner's compliance team | Regulated entities with own compliance program | | Document sharing | `POST /eapi/v0/identities/share/documents` | Banxa | Partners without KYC infrastructure | Models are not mutually exclusive. The same identity can be upgraded over time as transaction needs grow. --- ## Endpoints | Method | Path | Purpose | |---|---|---| | `POST` | `/eapi/v0/identities/basic` | Create identity with personal details | | `POST` | `/eapi/v0/identities/share/token` | Share Sumsub KYC token | | `POST` | `/eapi/v0/identities/reliance` | Identity reliance from regulated partner | | `POST` | `/eapi/v0/identities/share/documents` | Upload identity documents | | `PATCH` | `/eapi/v0/identities` | Update identity details | | `GET` | `/eapi/v0/identities/{identityReference}` | Retrieve identity and kyc.status | | `POST` | `/eapi/v0/identities/transactions/eligibility` | Check transaction eligibility | | `GET` | `/eapi/v0/identities/transactions/limits` | Get transaction limits | | `POST` | `/eapi/v0/identities/otp/verify` | Verify OTP server-to-server | | `GET` | `/eapi/v0/price` | Get real-time price quote | | `GET` | `/eapi/v0/quote` | Get locked quote with quoteId | | `POST` | `/eapi/v0/ramps` | Create on-ramp or off-ramp | | `GET` | `/eapi/v0/ramps` | List ramps for an identityReference | | `GET` | `/eapi/v0/ramps/{id}` | Get ramp details and status | --- ## Field Semantics ### identityReference - **Type:** String - **Format:** Alphanumeric and hyphens — `^[A-Za-z0-9-]+$` - **Purpose:** Partner's stable unique identifier per customer - **Rules:** Must remain constant for the same customer. Never change. Never contain PII. - **Example:** `customer-12345`, `user-abc-789` ### fiatAmount / cryptoAmount - **Type:** String (not number — avoids floating-point precision errors) - **Format:** Decimal string — fiat to 2 decimal places, crypto to up to 8 - **Rule:** Provide one or the other on ramp creation — never both - **Examples:** `"100.00"` (fiat), `"0.00250000"` (crypto) ### kyc.status | Value | Meaning | |---|---| | `PENDING` | No documents submitted yet | | `UNDER_REVIEW` | Documents being reviewed | | `ACTION_REQUIRED` | Customer action needed | | `VERIFIED` | Document and liveness checks passed | | `REJECTED` | Verification unsuccessful | Note: `VERIFIED` means documents passed — it does not guarantee transaction eligibility. Always check eligibility separately. ### Timestamps - **Format:** ISO 8601 UTC — `"2024-06-05T19:53:08.320Z"` - **NONCE:** Unix milliseconds — `Date.now()` in JavaScript --- ## Eligibility Requirements | Requirement | What to collect | |---|---| | `NAME` | Given name and surname | | `DOB` | Date of birth | | `ADDRESS` | Residential address | | `DOCUMENT` | Passport or government-issued ID | | `SELFIE` | Live selfie or liveness check (via Sumsub or Banxa-hosted flow) | | `OCCUPATION` | Occupation industry and job title (from predefined list) | | `SOURCE_FUNDS` | Source of funds declaration (e.g. `Salary`) | | `PURPOSE_OF_TX` | Transaction purpose (e.g. investment, remittance) | | `TIN` | Tax identification number (SSN or jurisdictional equivalent) | | `POA` | Proof of address (utility bill, bank statement) | Multiple requirements may be returned together. Collect them all before re-checking eligibility. --- ## State Machines ### Ramp Webhook Statuses On-ramp (fiat → crypto): ``` IN_PROGRESS → PAYMENT_READY (payment method ready) → PAYMENT_ACCEPTED (payment initiated) → PAYMENT_RECEIVED (payment confirmed) → COIN_TRANSFERRED (crypto sent to wallet) → FULFILLED (complete) Alternative exits: → PAYMENT_DECLINED → PAYMENT_CANCELLED → REFUNDED → EXPIRED → EXTRA_VERIFICATION (manual review required) → ACCOUNT_BLOCKED ``` Off-ramp (crypto → fiat): ``` IN_PROGRESS → COIN_DEPOSIT_READY (deposit address ready) → COIN_DEPOSIT_CONFIRMED (crypto received) → FIAT_TRANSFERRED (fiat sent to bank) → FULFILLED (complete) Alternative exits: → REFUNDED → EXPIRED → EXTRA_VERIFICATION → ACCOUNT_BLOCKED ``` ### KYC Status (from KYC webhooks) ``` PENDING → UNDER_REVIEW → VERIFIED → REJECTED → PENDING (resubmission) → ACTION_REQUIRED → (customer acts) → UNDER_REVIEW ``` --- ## Decision Trees ### Which identity model to use? ``` Partner uses Sumsub for KYC? └─→ YES → POST /identities/share/token Partner is a regulated entity with own compliance program? └─→ YES → POST /identities/reliance Partner wants Banxa to perform KYC from documents? └─→ YES → POST /identities/share/documents None of the above / low-value onboarding? └─→ POST /identities/basic ``` ### On-ramp or off-ramp? ``` Customer paying fiat to receive crypto? └─→ On-ramp: source.fiat + target.crypto in POST /ramps Customer sending crypto to receive fiat? └─→ Off-ramp: source.crypto + target.fiat in POST /ramps ``` ### Does this payment require the SDK? ``` Payment method is card, Apple Pay, or Google Pay? └─→ YES → React Native SDK required (PCI requirement) SDK presents native payment sheet via Primer Payment method is bank transfer (ACH, SEPA, PayID, Interac)? └─→ NO → API-only, no SDK needed Display sourceDepositInstructions to customer ``` --- ## Payment Method Specifics | Method | Code | Region | SDK required | Notes | |---|---|---|---|---| | Card | — | Global | Yes | Via Primer SDK | | Apple Pay | — | Global | Yes | Via Primer SDK | | Google Pay | — | Global | Yes | Via Primer SDK | | PayID bank transfer | `payid-bank-transfer` | Australia | No | Instructions include recipientEmail, depositReference | | SEPA bank transfer | `sepa-bank-transfer` | Europe | No | Instructions include IBAN, BIC | | ACH bank transfer | `ach-bank-transfer` | USA | No | Terms of service acceptance required in ramp creation payload | | Interac | `interac-bank-transfer` | Canada | No | Requires mobileNumber on identity; instructions include securityQuestion | **iDEAL, Klarna, PayPal**: require an external redirect outside the app — cannot run in a webview. Do not document as fully in-app. ### Blockchain memo/tag requirement Required for: XRP, XLM, EOS, ATOM. Field: `walletAddressMemo`. Critical: missing memo may result in permanent loss of funds. --- ## Error Handling | HTTP Code | Meaning | Action | |---|---|---| | `400` | Malformed request | Fix request structure | | `401` | Auth failure | Check HMAC signing — see HMAC error codes | | `404` | Resource not found | Verify identityReference or ramp ID | | `422` | Validation error | Fix field values — response includes per-field errors | | `429` | Rate limit exceeded | Exponential backoff; check for polling patterns | | `500` | Server error | Retry with exponential backoff, up to 3 times | **Rate limit:** 500 requests per minute per IP across all endpoints. --- ## Common Patterns ### Progressive compliance Start with basic identity for low-value transactions. Upgrade identity as transaction context demands higher verification. Eligibility drives what to collect — do not pre-collect more than required. ### Amount locking Provide `fiatAmount` XOR `cryptoAmount` on ramp creation — not both. Use the same field and value from the corresponding price request to guarantee the rate. ### Quote locking Use `GET /eapi/v0/quote` to lock a rate and get a `quoteId`. Pass `quoteId` to `POST /eapi/v0/ramps` to guarantee the quoted rate. Quotes expire in ~60 seconds. ### Idempotent webhook handling Use `order_id` + `status` as a deduplication key. Webhooks may be delivered more than once. --- ## Quick Reference | Task | Endpoint | |---|---| | Create identity | `POST /eapi/v0/identities/basic` | | Share Sumsub token | `POST /eapi/v0/identities/share/token` | | Check eligibility | `POST /eapi/v0/identities/transactions/eligibility` | | Get limits | `GET /eapi/v0/identities/transactions/limits` | | Get price | `GET /eapi/v0/price` | | Get locked quote | `GET /eapi/v0/quote` | | Create ramp | `POST /eapi/v0/ramps` | | Get ramp status | `GET /eapi/v0/ramps/{id}` | | List ramps | `GET /eapi/v0/ramps` | --- Source: https://docs.banxa.com/products/native-api/API-GUIDE.md # Banxa Native API — Integration Guide Step-by-step workflows and code examples for integrating the Banxa Native API. Read AI-METADATA.md first for field semantics, decision trees, and state machines. **Base URL (sandbox):** `https://api.banxa-sandbox.com` **Base URL (production):** `https://api.banxa.com` **Base path:** `/eapi/v0/` **Auth:** HMAC server-to-server on all requests --- ## Authentication All requests require HMAC-SHA256 authentication. **Canonical string:** ``` METHOD\nPATH\nNONCE\nBODY ``` - `PATH`: request path only — never include the domain - `NONCE`: `Date.now()` in JavaScript (Unix milliseconds) — unique per request - `BODY`: compact JSON (no whitespace) for POST/PATCH; empty string for GET **Authorization header:** ``` Authorization: Bearer API_KEY:SIGNATURE:NONCE ``` ```javascript // Node.js const crypto = require('crypto'); function generateHmac(method, path, nonce, body = '') { const message = [method, path, nonce, body].join('\n'); const signature = crypto.createHmac('sha256', API_SECRET).update(message).digest('hex'); return `Bearer ${API_KEY}:${signature}:${nonce}`; } function headers(method, path, body = null) { const nonce = Date.now().toString(); const bodyStr = body ? JSON.stringify(body) : ''; return { 'Authorization': generateHmac(method, path, nonce, bodyStr), 'Content-Type': 'application/json', }; } ``` ```python # Python import hmac, hashlib, time, json def generate_hmac(method, path, body=None): nonce = str(int(time.time() * 1000)) parts = [method, path, nonce] if body: parts.append(json.dumps(body, separators=(',', ':'))) message = '\n'.join(parts) signature = hmac.new(API_SECRET.encode(), message.encode(), hashlib.sha256).hexdigest() return f'Bearer {API_KEY}:{signature}:{nonce}', nonce ``` --- ## Workflow 1: Complete On-Ramp (Fiat → Crypto) ### Step 1: Establish identity For first-time users. If the user already has an identity on record, skip to Step 2. ```javascript // Basic identity (fastest — for low-value / low-risk transactions) const response = await fetch(`${BASE_URL}/eapi/v0/identities/basic`, { method: 'POST', headers: headers('POST', '/eapi/v0/identities/basic', { identityReference: 'customer-12345', givenName: 'Jane', surname: 'Smith', dateOfBirth: '1990-06-15', email: 'jane@example.com', residentialAddress: { addressLine: '42 Example St', suburb: 'Sydney', state: 'NSW', postCode: '2000', country: 'AU', }, }), body: JSON.stringify({ identityReference: 'customer-12345', givenName: 'Jane', surname: 'Smith', dateOfBirth: '1990-06-15', email: 'jane@example.com', residentialAddress: { addressLine: '42 Example St', suburb: 'Sydney', state: 'NSW', postCode: '2000', country: 'AU', }, }), }); ``` **Using Sumsub KYC token sharing** (if your platform uses Sumsub): ```javascript const body = { identityReference: 'customer-12345', email: 'jane@example.com', mobileNumber: '+61400000001', provider: { vendor: 'sumsub', token: 'SUMSUB_ACCESS_TOKEN', // generated via Sumsub API using Banxa's clientId }, }; await fetch(`${BASE_URL}/eapi/v0/identities/share/token`, { method: 'POST', headers: headers('POST', '/eapi/v0/identities/share/token', body), body: JSON.stringify(body), }); // Sumsub token sharing is processed asynchronously. // Wait for kyc.status = VERIFIED via KYC webhook before checking eligibility. ``` ### Step 2: Check eligibility Call before every transaction — not once at onboarding. Eligibility is evaluated against the specific transaction context. ```javascript const body = { identityReference: 'customer-12345', transactionType: 'ONRAMP', fiat: 'AUD', crypto: 'ETH', blockchain: 'ETH', method: 'payid-bank-transfer', fiatAmount: '200.00', }; const eligibility = await fetch(`${BASE_URL}/eapi/v0/identities/transactions/eligibility`, { method: 'POST', headers: headers('POST', '/eapi/v0/identities/transactions/eligibility', body), body: JSON.stringify(body), }).then(r => r.json()); // { paymentReady: true, requirements: [] } // — or — // { paymentReady: false, requirements: ['DOB', 'ADDRESS'] } if (!eligibility.paymentReady) { // Collect requirements in your UX, submit via PATCH /identities, then re-check throw new Error(`Requirements outstanding: ${eligibility.requirements.join(', ')}`); } ``` ### Step 3: Get price ```javascript const params = new URLSearchParams({ identityReference: 'customer-12345', transactionType: 'ONRAMP', fiat: 'AUD', crypto: 'ETH', blockchain: 'ETH', method: 'payid-bank-transfer', fiatAmount: '200', }); const path = `/eapi/v0/price?${params}`; const quote = await fetch(`${BASE_URL}${path}`, { headers: headers('GET', path), }).then(r => r.json()); // quote.source.amount — fiat to pay // quote.target.amount — crypto to receive // quote.processingFee — Banxa fee // quote.networkFee — blockchain fee // quote.marketRate — reference market rate at quote time ``` For a locked quote (guarantees the rate): ```javascript // GET /eapi/v0/quote returns a quoteId — pass it to ramp creation const lockedQuote = await fetch(`${BASE_URL}/eapi/v0/quote?${params}`, { headers: headers('GET', `/eapi/v0/quote?${params}`), }).then(r => r.json()); // lockedQuote.quoteId — pass this to POST /ramps ``` ### Step 4: Create the ramp ```javascript const body = { identityReference: 'customer-12345', source: { fiat: { id: 'AUD', method: 'payid-bank-transfer', }, }, target: { crypto: { id: 'ETH', blockchain: 'ETH', walletAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', }, }, fiatAmount: '200.00', // quoteId: lockedQuote.quoteId, // optional — include to lock the rate }; const ramp = await fetch(`${BASE_URL}/eapi/v0/ramps`, { method: 'POST', headers: headers('POST', '/eapi/v0/ramps', body), body: JSON.stringify(body), }).then(r => r.json()); // ramp.id — use for status lookups // ramp.sourceDepositInstructions — show to customer for bank transfer ``` ### Step 5: Display payment instructions (bank transfer) ```javascript const { recipientEmail, depositReference } = ramp.sourceDepositInstructions; showToCustomer({ payTo: recipientEmail, // PayID email address reference: depositReference, // Must be included in the bank transfer amount: '200.00 AUD', }); ``` For card, Apple Pay, or Google Pay — use the React Native SDK: ```javascript import { Banxa } from '@banxa-official/react-native-sdk'; // The SDK presents a native Primer payment sheet // See SDK reference for setup and peer dependencies ``` ### Step 6: Handle webhooks ```javascript app.post('/webhooks/banxa', (req, res) => { // Return 200 immediately — process asynchronously res.status(200).send('OK'); handleRampWebhook(req.body).catch(console.error); }); async function handleRampWebhook({ order_id, status, external_reason }) { // Deduplication: skip if already processed if (await isAlreadyProcessed(order_id, status)) return; switch (status) { case 'PAYMENT_RECEIVED': await notifyUser('Payment confirmed. Processing...'); break; case 'COIN_TRANSFERRED': await notifyUser('Crypto sent to your wallet.'); break; case 'FULFILLED': await markOrderComplete(order_id); break; case 'PAYMENT_DECLINED': case 'PAYMENT_CANCELLED': await notifyUser(`Payment issue: ${external_reason}`); break; case 'EXTRA_VERIFICATION': await notifyUser('Additional verification required. Banxa will be in touch.'); break; case 'ACCOUNT_BLOCKED': await flagAccountForReview(order_id); break; } await markProcessed(order_id, status); } ``` --- ## Workflow 2: Off-Ramp (Crypto → Fiat) Steps 1 and 2 (identity and eligibility) are identical to on-ramp. Change `transactionType` to `OFFRAMP`. ```javascript // Step 3: Get price const params = new URLSearchParams({ identityReference: 'customer-12345', transactionType: 'OFFRAMP', fiat: 'AUD', crypto: 'USDT', blockchain: 'TRON', method: 'payid-bank-transfer', cryptoAmount: '100', // lock crypto side for off-ramp }); // Step 4: Create ramp const body = { identityReference: 'customer-12345', source: { crypto: { id: 'USDT', blockchain: 'TRON', walletAddress: '0xCustomerSendingWalletAddress', }, }, target: { fiat: { id: 'AUD', method: 'payid-bank-transfer', instructions: { accountName: 'Jane Smith', accountNumber: '12345678', bsb: '063123', }, }, }, cryptoAmount: '100.00', }; const ramp = await fetch(`${BASE_URL}/eapi/v0/ramps`, { method: 'POST', headers: headers('POST', '/eapi/v0/ramps', body), body: JSON.stringify(body), }).then(r => r.json()); // Show customer where to send crypto showToCustomer({ sendCryptoTo: ramp.sourceDepositInstructions.walletAddress, memo: ramp.sourceDepositInstructions.walletAddressMemo, // include if present amount: '100 USDT on TRON', }); ``` --- ## Workflow 3: Identity Updates (Collecting Requirements) When eligibility returns outstanding requirements: ```javascript // Collect NAME, DOB, ADDRESS in your UX, then: const patch = { identityReference: 'customer-12345', givenName: 'Jane', surname: 'Smith', dateOfBirth: '1990-06-15', residentialAddress: { addressLine: '42 Example St', suburb: 'Sydney', state: 'NSW', postCode: '2000', country: 'AU', }, }; await fetch(`${BASE_URL}/eapi/v0/identities`, { method: 'PATCH', headers: headers('PATCH', '/eapi/v0/identities', patch), body: JSON.stringify(patch), }); // Then re-check eligibility with the same transaction context ``` --- ## Webhook Verification ```javascript const crypto = require('crypto'); const MY_WEBHOOK_PATH = '/webhooks/banxa'; // your endpoint path — not a Banxa path function verifyWebhook(authHeader, rawBody) { const token = authHeader.replace('Bearer ', ''); const [, receivedSignature, nonce] = token.split(':'); const message = `POST\n${MY_WEBHOOK_PATH}\n${nonce}\n${rawBody}`; const expected = crypto.createHmac('sha256', API_SECRET).update(message).digest('hex'); // Always use timing-safe comparison return crypto.timingSafeEqual( Buffer.from(receivedSignature), Buffer.from(expected) ); } ``` --- ## Error Handling and Retry ```javascript async function apiRequest(method, path, body = null, maxRetries = 3) { const bodyStr = body ? JSON.stringify(body) : null; for (let attempt = 0; attempt < maxRetries; attempt++) { const nonce = Date.now().toString(); const auth = generateHmac(method, path, nonce, bodyStr || ''); const res = await fetch(`${BASE_URL}${path}`, { method, headers: { Authorization: auth, 'Content-Type': 'application/json' }, body: bodyStr, }); if (res.status === 429) { const retryAfter = parseInt(res.headers.get('Retry-After') || '60', 10); await sleep(retryAfter * 1000); continue; } if (res.status >= 500) { await sleep(Math.pow(2, attempt) * 1000); continue; } if (!res.ok) { const err = await res.json(); // 422 errors include per-field validation details throw Object.assign(new Error(err.message), { status: res.status, detail: err.errors }); } return res.status === 204 ? null : res.json(); } throw new Error('Max retries exceeded'); } ``` --- ## Best Practices **identityReference management** - Use a stable, opaque identifier — never email, name, or internal DB ID - Store the mapping server-side; never expose it client-side - Consistent format across all calls: `partner-{userId}` or UUID **Eligibility** - Check eligibility every transaction — context changes affect requirements - Collect all outstanding requirements before re-checking; don't check one at a time - Do not pre-collect more than eligibility requires **Quotes** - Refresh quotes every 30 seconds when displaying to customers - Use `GET /eapi/v0/quote` (not `/price`) to lock a rate before ramp creation - Never use a stale quote — prices change **ACH bank transfers** - `POST /eapi/v0/ramps` for ACH requires terms of service acceptance in the payload - Check the API Reference for the required field **Memos / tags** - Always surface `walletAddressMemo` to customers when present on off-ramp instructions - XRP, XLM, EOS, ATOM require a memo — missing memo can result in permanent loss of funds **Webhooks** - Return `200 OK` immediately; process asynchronously - Implement idempotency: `order_id + status` as deduplication key - Log all incoming webhooks before processing --- Source: https://docs.banxa.com/products/hosted-checkout/AI-METADATA.md # Banxa Hosted Checkout — AI Metadata Structured reference for AI systems working with the Banxa Hosted Checkout. Read AI-OVERVIEW.md first for cross-product context. **Current version:** v2 **Auth:** x-api-key (HMAC for KYC sharing only) **Base path:** `/{partnerRef}/v2/` --- ## Core Concept Banxa Hosted Checkout is a hosted ramp infrastructure. Banxa manages the checkout experience — KYC, payment processing, and crypto delivery. Partners construct an order, receive a `checkoutUrl`, and redirect or embed it. Three integration paths: 1. **Referral** — Construct a Banxa URL with query parameters and redirect the customer. No API calls, no backend required. 2. **API** — Server-to-server calls using `x-api-key`. Create orders, receive webhooks, look up orders, share KYC data. Backend required only for KYC sharing. 3. **React Native SDK** (`@banxa-official/react-native-sdk`) — Typed SDK for React Native mobile apps. Wraps API calls and presents checkout in a WebView. No backend required for core flows. --- ## Key Entities **Order** — a transaction converting between fiat and crypto - On-ramp: fiat → crypto (buy) via `POST /v2/buy` - Off-ramp: crypto → fiat (sell) via `POST /v2/sell` - Tracked by unique `id` (Banxa) and optionally by `externalOrderId` (partner-provided) - Lifecycle tracked via `status` field and webhooks **Quote** — real-time price information - Short-lived (~60 seconds) - Use `GET /v2/quotes/{orderType}` before showing a price to customers - Includes processing fee and network fee **externalCustomerId** — partner-managed stable customer identifier - Required on `POST /v2/buy` and `POST /v2/sell` — not optional - Enables returning customer recognition and reduces repeat KYC - Must not contain PII; must be consistent for the same customer across all calls --- ## Domain Model ``` Customer (externalCustomerId) ├── Identity (KYC via Banxa checkout, optionally pre-shared) │ ├── kyc.status: PENDING → UNDER_REVIEW → VERIFIED / REJECTED │ └── account.blocked: boolean │ └── Orders ├── On-ramp: POST /v2/buy → checkoutUrl └── Off-ramp: POST /v2/sell → checkoutUrl ``` --- ## Integration Paths — Decision Tree ``` Partner platform? │ ├─→ React Native mobile app? │ └─→ React Native SDK (typed methods, built-in WebView, no backend needed) │ ├─→ Web or any platform with backend? │ └─→ API integration (x-api-key, webhooks, KYC sharing available) │ └─→ Web only, no backend, fastest go-live? └─→ Referral (URL construction, redirect, no API calls) Need KYC sharing? └─→ API only (requires HMAC, server-side only; not available via SDK or Referral) Need webhooks? └─→ API or SDK (Referral has no webhooks) ``` --- ## Endpoints | Method | Path | Auth | Purpose | |---|---|---|---| | `POST` | `/{partnerRef}/v2/buy` | x-api-key | Create on-ramp order, returns checkoutUrl | | `POST` | `/{partnerRef}/v2/sell` | x-api-key | Create off-ramp order, returns checkoutUrl | | `GET` | `/{partnerRef}/v2/quotes/{orderType}` | x-api-key | Get real-time price quote | | `GET` | `/{partnerRef}/v2/orders` | x-api-key | List orders | | `GET` | `/{partnerRef}/v2/orders/{id}` | x-api-key | Get order by Banxa ID | | `GET` | `/{partnerRef}/v2/payment-methods/{orderType}` | x-api-key | List supported payment methods | | `GET` | `/{partnerRef}/v2/fiats/{orderType}` | x-api-key | List supported fiat currencies | | `GET` | `/{partnerRef}/v2/crypto/{orderType}` | x-api-key | List supported cryptocurrencies | | `GET` | `/{partnerRef}/v2/countries` | x-api-key | List supported countries | | `POST` | `/{partnerRef}/v2/identities/token/share` | HMAC | Share Sumsub KYC token | | `POST` | `/{partnerRef}/v2/orders/{id}/confirm` | x-api-key | Confirm custodial crypto transfer | `orderType` query parameter: `buy` or `sell` --- ## Authentication ### x-api-key (standard) Used for all endpoints except KYC sharing. ``` x-api-key: YOUR_API_KEY ``` ### HMAC (KYC sharing only) Used for `POST /v2/identities/token/share`. Must be signed server-side — never in frontend or mobile code. ``` Authorization: Bearer API_KEY:SIGNATURE:NONCE ``` Canonical string for POST: ``` METHOD\nPATH\nNONCE\nCOMPACT_JSON_BODY ``` Canonical string for GET: ``` METHOD\nPATH_WITH_QUERY_STRING\nNONCE ``` HMAC error codes: `40001` (invalid nonce), `40002` (nonce too old), `40003` (nonce reused), `40100` (key not recognised), `40101` (header malformed), `40102` (header missing), `40103` (signature mismatch). --- ## Field Semantics ### externalCustomerId - **Type:** String - **Required:** Yes on `POST /v2/buy` and `POST /v2/sell` - **Purpose:** Partner's stable unique identifier per customer - **Rules:** Must remain constant for the same customer. Never contain PII. - **Effect:** Enables returning customer recognition; KYC pre-fill when combined with KYC sharing ### fiatAmount / cryptoAmount (buy) - Provide one or the other — not both - Type: String (not number) - Fiat to 2 decimal places; crypto to up to 8 ### cryptoAmount (sell) - Required on `POST /v2/sell` (off-ramp locks the crypto side) ### checkoutUrl - Returned by both `POST /v2/buy` and `POST /v2/sell` - Redirect the customer to this URL, or load it in a WebView/iFrame - Short-lived — do not cache ### Order status - Type: camelCase string enum (not UPPER_SNAKE_CASE) - See state machine below ### Timestamps - Format: ISO 8601 UTC — `"2024-06-05T19:53:08.320Z"` - NONCE: Unix milliseconds — `Date.now()` in JavaScript --- ## State Machine — Order Lifecycle ### On-ramp (buy) statuses ``` pendingPayment → Order created; awaiting customer payment waitingPayment → Payment info submitted; awaiting external confirmation (may be skipped for instant methods like card, Apple Pay) paymentReceived → Fiat payment confirmed inProgress → Final verification and processing cryptoTransferred → Crypto submitted to blockchain complete → Done (after 2 blockchain confirmations) Alternative exits: cancelled → Cancelled by Banxa (risk/compliance) declined → Declined by external payment system expired → Payment not received in time (not always terminal — can resume) refunded → Refunded by Banxa support extraVerification → Held for additional verification; will resume when resolved ``` ### Off-ramp (sell) statuses ``` pendingPayment → Order created; awaiting conditions before crypto can be accepted waitingPayment → All conditions met; customer must send crypto to Banxa address (custodial signal: execute the crypto transfer now) paymentReceived → Crypto received by Banxa inProgress → Fiat payout processing complete → Fiat sent to customer Same alternative exits as on-ramp. ``` ### Terminal statuses `complete`, `cancelled`, `declined`, `refunded` Note: `expired` is not always terminal — orders can resume if payment arrives after expiry. --- ## Webhook Types ### 1. Order webhooks Triggered on every status transition. Key fields: - `order_id` — Banxa order ID - `status` — new order status (camelCase) - `order_type` — `BUY` or `SELL` - `external_id` — partner's `externalOrderId` if provided ### 2. KYC webhooks Triggered on identity verification state changes. Must be enabled by Banxa. Key fields: - `external_customer_id` - `kyc.status` — `PENDING` | `UNDER_REVIEW` | `ACTION_REQUIRED` | `VERIFIED` | `REJECTED` - `account.blocked` — boolean `kyc.status = VERIFIED` means documents passed — it does not guarantee transaction eligibility. ### 3. EDD (Enhanced Due Diligence) webhooks Triggered when manual review is required. Must be enabled by Banxa. Key fields: - `identity_reference` - `status` — typically `extraVerification` - `external_reason` — display to customer Triggers: EDD review, verification phone call, questionnaire, proof of address, source of funds. ### 4. Account blocked webhooks Triggered when a customer is restricted from transacting. Key fields: - `identity_reference` - `status` — `cancelled` - `external_reason` — display to customer ### Webhook security All webhooks are signed with HMAC-SHA256 using the same algorithm as request signing. Canonical string uses **your own webhook endpoint path** — not a Banxa API path. ``` POST\nYOUR_WEBHOOK_PATH\nNONCE\nPAYLOAD ``` ### Retry behaviour Fibonacci sequence: 1s, 2s, 3s, 5s, 8s, 13s... Up to 18 retries over 2 hours. --- ## Payment Methods ### Buy (on-ramp) | Method ID | Description | Region | |---|---|---| | `debit-credit-card` | Debit or credit card | Global | | `apple-pay` | Apple Pay | iOS/macOS | | `google-pay` | Google Pay | Android | | `payid-bank-transfer` | PayID bank transfer | Australia | | `sepa-bank-transfer` | SEPA bank transfer | Europe | | `ach-bank-transfer` | ACH bank transfer | USA | | `interac-bank-transfer` | Interac | Canada | | `upi` | UPI | India | | `pix` | PIX | Brazil | | `paypal` | PayPal | Selected regions | ### Sell (off-ramp) | Method ID | Payout | Region | |---|---|---| | `payid-bank-transfer` | PayID (AUD) | Australia | | `sepa-bank-transfer` | SEPA (EUR) | Europe | | `pix` | PIX (BRL) | Brazil | ### WebView constraints - iDEAL, Klarna, PayPal: under investigation — a Hosted Checkout partner has confirmed running iDEAL and PayPal inside a WebView successfully. Do not document as hard constraints until confirmed with Engineering. - Apple Pay on web iFrame: redirects to a new Banxa tab — does not work inside iFrame on web - Google Pay on Android WebView: may require Custom Chrome Tabs (under investigation) --- ## Sell Flow Variants **Non-custodial** — customer initiates the crypto transfer themselves. Banxa shows a wallet address in checkout; customer sends crypto manually. **Custodial** — partner executes the crypto transfer on behalf of the customer. 1. Customer completes checkout 2. Banxa sends `waitingPayment` webhook 3. Partner executes crypto transfer to Banxa wallet address 4. Partner calls `POST /v2/orders/{id}/confirm` to notify Banxa 5. Banxa processes fiat payout --- ## KYC Sharing (Sumsub) Call `POST /v2/identities/token/share` at the time of KYC verification on your platform — not at order creation (data may not be processed in time). Required fields: `externalCustomerId`, `email`, `mobileNumber`, `provider.vendor` (`"sumsub"`), `provider.token` (Sumsub share token using Banxa's `clientId: banxa.com_5335`). Returns `202 Accepted` — processing is asynchronous. `externalCustomerId` must match exactly what is used in `POST /v2/buy` / `POST /v2/sell`. Mismatches create duplicate identities. Only submit for customers who are already KYC-verified on your platform. --- ## Error Handling | HTTP Code | Meaning | Action | |---|---|---| | `400` | Malformed request | Fix request structure | | `401` | Auth failure | Check x-api-key or HMAC signing | | `404` | Resource not found | Verify order ID | | `422` | Validation error | Fix field values — response includes per-field errors | | `429` | Rate limit exceeded | Exponential backoff; use webhooks instead of polling | | `500` | Server error | Retry with exponential backoff, up to 3 times | **Rate limit:** 500 requests per minute per IP. --- ## React Native SDK Package: `@banxa-official/react-native-sdk` Peer dependencies: `react-native-webview`, `@primer-io/react-native` Key modules: - `banxa.buy` — order creation and lookup - `banxa.prices` — quotes - `banxa.paymentMethods` — payment method list - `banxa.currencies` — fiat and crypto currency lists - `banxa.countries` — country list Key methods: - `banxa.buy.createOrder(params)` → POST /v2/buy - `banxa.buy.getOrder(id)` → GET /v2/orders/{id} - `banxa.buy.getOrdersByAccount('id')` → orders filtered by customer (not full order list) - `banxa.buy.initializeCheckoutWebView(order, options)` → configures CheckoutWebView props - `banxa.prices.getQuote('buy'|'sell', params)` - `banxa.prices.getBuyQuote(params)` Limitations: does not support KYC sharing (requires HMAC auth). No `banxa.customerIdentity` module. --- ## Blockchain Memo/Tag Requirement Required for: XRP, XLM, EOS, ATOM. Field: `walletAddressTag` on `POST /v2/buy` and `POST /v2/sell`. Missing tag may result in permanent loss of funds. --- ## Quick Reference | Task | Endpoint | Auth | |---|---|---| | Create on-ramp order | `POST /v2/buy` | x-api-key | | Create off-ramp order | `POST /v2/sell` | x-api-key | | Get price quote | `GET /v2/quotes/{orderType}` | x-api-key | | Get order status | `GET /v2/orders/{id}` | x-api-key | | List orders | `GET /v2/orders` | x-api-key | | Share KYC token | `POST /v2/identities/token/share` | HMAC | | Confirm custodial transfer | `POST /v2/orders/{id}/confirm` | x-api-key | | List payment methods | `GET /v2/payment-methods/{orderType}` | x-api-key | | List currencies | `GET /v2/fiats/{orderType}` or `GET /v2/crypto/{orderType}` | x-api-key | --- Source: https://docs.banxa.com/products/hosted-checkout/API-GUIDE.md # Banxa Hosted Checkout — Integration Guide Step-by-step workflows and code examples for integrating Banxa Hosted Checkout. Read AI-METADATA.md first for field semantics, decision trees, and state machines. **Base URL (sandbox):** `https://api.banxa-sandbox.com` **Base URL (production):** `https://api.banxa.com` **Base path:** `/{partnerRef}/v2/` **Auth:** `x-api-key` header on all endpoints (HMAC for KYC sharing only) --- ## Integration Path Overview **Referral** — no API calls, no backend. Construct a URL, redirect the customer. **API** — x-api-key server-to-server. Create orders, receive webhooks, share KYC. **React Native SDK** — typed SDK for React Native. Same API calls wrapped in TypeScript. --- ## Workflow 1: Referral Integration Construct a URL from your base referral URL and redirect the customer. No API key or backend required. ``` https://[partner].banxa.com/? walletAddress=0xe3BDEFdAeFF070925eB7FfC49F9B30c647Cb751e &coinType=ETH &blockchain=ETH &fiatType=AUD &fiatAmount=200 &orderType=buy &redirectUrl=https://yourapp.com/order-complete &externalCustomerId=user_12345 ``` Key parameters: - `walletAddress` — customer's receiving wallet address - `coinType` — cryptocurrency code (`ETH`, `BTC`, `USDT`) - `blockchain` — blockchain network (required for multi-chain assets) - `fiatType` — fiat currency code (`AUD`, `USD`, `EUR`) - `fiatAmount` or `coinAmount` — provide one - `orderType` — `buy` or `sell` - `redirectUrl` — where to send the customer after checkout - `externalCustomerId` — your stable customer identifier For sell orders, include `coinAmount` and add bank details as parameters. The JavaScript SDK (`@banxa-official/js-sdk`) is a convenience wrapper for building referral URLs — it is not a separate integration layer and does not make API calls. --- ## Workflow 2: API Integration — On-Ramp (Fiat → Crypto) ### Step 1: Get a quote (optional but recommended) Show the customer a price before they enter checkout. ```bash curl "https://api.banxa-sandbox.com/{partnerRef}/v2/quotes/buy?\ fiatType=AUD&coinType=ETH&fiatAmount=200&blockchain=ETH&paymentMethodId=debit-credit-card" \ -H "x-api-key: YOUR_API_KEY" ``` ```json { "fiatType": "AUD", "coinType": "ETH", "fiatAmount": "200.00", "coinAmount": "0.04812", "processingFee": "6.00", "networkFee": "1.20", "exchangeRate": "4156.23" } ``` Refresh quotes every 30 seconds when displaying to customers — prices change. ### Step 2: Create the order ```javascript const body = { crypto: 'ETH', blockchain: 'ETH', fiat: 'AUD', fiatAmount: '200', walletAddress: '0xe3BDEFdAeFF070925eB7FfC49F9B30c647Cb751e', redirectUrl: 'https://yourapp.com/order-complete', paymentMethodId: 'debit-credit-card', externalCustomerId: 'user_12345', email: 'customer@example.com', externalOrderId: 'order-abc-789', // optional — your reference for reconciliation }; const order = await fetch( `${BASE_URL}/{partnerRef}/v2/buy`, { method: 'POST', headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify(body), } ).then(r => r.json()); // order.id — Banxa order ID; store for lookups // order.checkoutUrl — redirect or embed this ``` ### Step 3: Present checkout **Redirect:** ```javascript window.location.href = order.checkoutUrl; ``` **iFrame:** ```html ``` For mobile WebView implementation, specific platform configuration is required. → See [Embedded Checkout — Web](../checkout-experience/iframe/iframe-web.md) and [Embedded Checkout — Mobile](../checkout-experience/iframe/webview-mobile.md). --- ## Checkout URL expiry The `checkoutUrl` is valid for a limited time. If the customer does not complete checkout before the URL expires, the order status will move to `expired` and the customer will need to restart. --- ## After checkout When the transaction is complete, the customer is redirected to your `redirectUrl`. To get the final order status programmatically, either: - Listen for a **webhook** notification (recommended). - Poll the **order lookup** endpoint. → See [Webhooks](../transaction-lifecycle/webhooks.md) and [Order Lookup](../transaction-lifecycle/order-lookup.md). --- Source: https://docs.banxa.com/products/hosted-checkout/docs/transaction-lifecycle/order-statuses.md --- title: "Order Status Reference Guide | Banxa Docs" description: "All Banxa order statuses: pendingPayment, waitingPayment, paymentReceived, inProgress, complete, and more — with distinct buy and sell flow definitions." --- # Order Statuses Every Banxa order moves through a sequence of statuses from creation to completion. The order response from `POST /v2/buy`, `POST /v2/sell`, and `GET /v2/orders` all include an order status field. You may want to group or map these statuses into something more meaningful for the experience you present to your customers. --- ## Status reference | Status | Definition | |---|---| | `pendingPayment` | Order has been created and the customer has submitted KYC information. **Buy:** waiting for customer payment. **Sell:** waiting for required conditions before Banxa can accept the crypto payment. | | `waitingPayment` | **Buy:** customer has submitted payment information; waiting for final confirmation from external payment systems. **Sell:** all conditions are met and the customer needs to send crypto to the provided wallet address. This webhook signals that cryptocurrency is ready to be sent to Banxa. | | `paymentReceived` | Payment confirmed. **Buy:** fiat payment received. **Sell:** crypto payment received. | | `inProgress` | Payment information has been received by external payment systems. The order is in final verification and processing. | | `cryptoTransferred` | Cryptocurrency transaction has been submitted to the blockchain. | | `complete` | Order completed. **Buy:** deemed complete after 2 blockchain confirmations. **Sell:** deemed complete when fiat has been successfully sent to the customer. | | `cancelled` | Order cancelled by Banxa due to internal risk and compliance alerts. | | `declined` | Order declined by external payment systems. | | `expired` | Order created but payment not received within the expiry window. Expiry times vary by payment method. **Note:** expired orders can become active again — if payment is received after expiry, the order will automatically continue through the normal processing stages. | | `refunded` | Order refunded by Banxa customer support at the customer's request. | | `extraVerification` | Order held for additional verification (e.g., ID or address verification). Banxa customer support will contact the customer to resolve the order. | --- ## Typical on-ramp (buy) flow ```mermaid flowchart LR A([Order created]) -->|pendingPayment| B[KYC] B -->|pendingPayment| C[Payment] C -->|cancelled / declined| D([cancelled / declined]) D -->|timeout| E([expired]) C -->|paymentReceived| F[Order\nexecution] F -->|inProgress| G{Verified?} G -->|cryptoTransferred| H[Crypto sent\nto wallet] H -->|complete| Z([complete]) G -->|inProgress| I[Manual\nreview] I -->|resolved| Z I -->|timeout| E ``` For instant payment methods (card, Apple Pay), `waitingPayment` may be bypassed. ## Typical off-ramp (sell) flow ```mermaid flowchart LR A([Order created]) -->|pendingPayment| B[KYC + bank\ndetails] B -->|waitingPayment| C[Awaiting\ncrypto transfer] C -->|cancelled / declined| D([cancelled / declined]) D -->|timeout| E([expired]) C -->|paymentReceived| F[Fiat payout\nprocessing] F -->|inProgress| G{Verified?} G -->|complete| Z([complete]) G -->|inProgress| I[Manual\nreview] I -->|resolved| Z I -->|timeout| E ``` `waitingPayment` is the signal for custodial integrations to execute the crypto transfer. Once Banxa receives the crypto (`paymentReceived`), the fiat payout is processed. --- ## Terminal statuses The following statuses are final — the order will not progress further: - `complete` - `cancelled` - `declined` - `refunded` > **Note:** `expired` is not always terminal. Orders can resume if payment is received after the expiry time. `extraVerification` is a hold state — the order will resume once verification is resolved by Banxa support. --- ## Checking order status You can retrieve the current status of any order via the order lookup endpoint, or receive status updates automatically via webhooks. → See [Order Lookup](./order-lookup.md) and [Webhooks](./webhooks.md). --- Source: https://docs.banxa.com/products/hosted-checkout/docs/transaction-lifecycle/webhooks.md --- title: "Webhooks | Banxa Docs" description: "Banxa webhook notifications for order status changes and KYC/identity events. Payload reference, retry behaviour, and HMAC signature verification." --- # Webhooks Banxa sends webhook notifications to your webhook endpoint URL whenever an order's status changes or a user's KYC/Account state updates. This eliminates the need to poll our APIs and allows you to track the complete user journey in real-time. --- ## Setup Configure your webhook endpoint URL in the [Partner Dashboard](https://dashboard.banxa.com/). You can set separate URLs for sandbox and production. The webhook URL must: - Be publicly accessible over HTTPS. - Return a `200` HTTP response to acknowledge receipt. --- ## Available Webhooks ### 1. Order Webhooks Order webhooks are triggered on all order status transitions. We will send an HTTP `POST` request with a JSON body. **Payload Example:** ```json { "order_id": "d9efc5d228cb7edfc4b6bb82f7b39f94", "status": "complete", "status_date": "2026-01-1604:04:21", "created_at": "2026-01-1604:04:20", "updated_at": "2026-01-1604:04:20", "external_id": null, "order_type": "BUY", "crypto_coin": "USDT", "crypto_blockchain": "ETH", "crypto_amount": "67.1000000000000000", "fiat_currency": "AUD", "fiat_amount": "100", "asset_price": "1.490312965722801", "payment_method": "payid-bank-transfer", "processing_fee": "0", "network_fee": "0", "usd_exchange_rate": "1.4923330", "transaction_hash": "0", "metadata": [] } ``` | Field | Description | | :--- | :--- | | `order_id` | The unique Banxa identifier for the order. | | `status` | The new order status. See [Order Statuses](./order-statuses.md) for all possible values. | | `status_date` | ISO 8601 timestamp of the most recent status transition. | | `created_at` | ISO 8601 timestamp of when the order was initially created. | | `updated_at` | ISO 8601 timestamp of the last time the order record was modified. | | `external_id` | The unique ID provided by the partner during order creation (if applicable). | | `order_type` | The transaction direction: `BUY` (Fiat to Crypto) or `SELL` (Crypto to Fiat). | | `crypto_coin` | The ticker symbol of the cryptocurrency (e.g., `USDT`, `BTC`). | | `crypto_blockchain` | The specific network/blockchain used for the transaction (e.g., `ETH`, `SOL`). | | `crypto_amount` | The total amount of cryptocurrency involved in the transaction. | | `fiat_currency` | The 3-letter ISO code of the fiat currency used (e.g., `AUD`, `CAD`). | | `fiat_amount` | The total fiat amount of the transaction. | | `asset_price` | The price of one unit of the cryptocurrency in the source fiat currency. | | `payment_method` | The specific payment rail used for the transaction (e.g., `payid-bank-transfer`). | | `processing_fee` | The service/gateway fee charged by Banxa in the source fiat currency. | | `network_fee` | The blockchain network (gas) fee in the source fiat currency. | | `usd_exchange_rate` | The exchange rate used to convert 1 unit of source fiat to USD at the time of order creation. | | `transaction_hash` | The on-chain identifier (TXID) for the crypto transfer, if available. | | `metadata` | A collection of custom key-value pairs passed by the partner for tracking purposes. | After receiving a webhook, use the `order_id` to fetch full order details from the [order lookup endpoint](./order-lookup.md) if you need additional data. --- ### 2. Identity & KYC Webhooks #### 2.1 KYC Webhooks KYC webhooks are triggered whenever a user's identity verification state changes. Please reach out to your Banxa contact if you would like to have this webhook enabled. **Payload Example:** ```json { "external_customer_id": "demomerchant-61466523855", "account": { "exists": true, "blocked": false, "createdAt": "2026-03-30T05:08:11Z" }, "kyc": { "status": "UNDER_REVIEW" } } ``` | Field | Description | | :--- | :--- | | `external_customer_id` | Your system's unique identifier for the user. | | `account.exists` | Boolean indicating if the user profile is successfully created in our system. | | `account.blocked` | Boolean indicating if the user has been banned, suspended, or hit a compliance block. | | `account.createdAt` | ISO 8601 timestamp of account creation. | | `kyc.status` | The verification outcome of the customer's submitted identity documents (selfie + document). See status table below. | **`kyc.status` values:** | Status | Description | | :--- | :--- | | `PENDING` | No identity documents have been submitted yet. | | `UNDER_REVIEW` | Documents are being reviewed. | | `ACTION_REQUIRED` | Additional action is required from the customer. | | `VERIFIED` | Document and liveness verification passed. | | `REJECTED` | Verification was unsuccessful. | > **Important:** `kyc.status` reflects document and liveness verification only — it does not account for supplementary fields (e.g. purpose of transaction, occupation) or overall transaction eligibility. A `VERIFIED` status means the identity documents passed review; it does not mean the customer is eligible to transact. Use the order creation flow to determine whether a customer can proceed. --- #### 2.2 Enhanced Due Diligence (EDD) Webhook This identity-level webhook is triggered when an order requires manual intervention or additional documentation (Enhanced Due Diligence). It allows you to monitor when a user has been flagged for specific verification steps beyond standard KYC. Please reach out to your Banxa contact if you would like to have this webhook enabled. **Payload Example:**: ```json { "identity_reference": "demomerchant-61466233701", "status": "extraVerification", "status_date": "2026-02-13 04:39:38", "internal_reason": "Customer requires extra verification", "external_reason": "Your order is pending additional verification. We'll notify you once it's complete" } ``` | Field | Description | | :--- | :--- | | `identity_reference` | The unique identifier for the user (matches `external_customer_id` or `account_reference`). | | `status` | The verification status (typically `extraVerification`). | | `status_date` | ISO 8601 timestamp of when the additional verification was triggered. | | `internal_reason` | The internal system categorization for the verification request. | | `external_reason` | The user-facing message explaining why the transaction is pending. | **Triggers:** This webhook is sent when any of the following identity-level exceptions are required: * **Enhanced Due Diligence (EDD):** High-level manual review. * **Verification Phone Call (VPC):** Requirement for a manual call with the user. * **Questionnaire:** Scam check or suitability assessment. * **Proof of Address (POA):** Request for residency documentation. * **Source of Funds (SOF):** Request for documentation regarding the origin of funds. --- #### 2.3 Account Blocked Webhook This identity-level webhook is triggered when a customer has been restricted from creating or completing orders due to compliance or risk policies. It provides the necessary context to inform your internal teams or the end user. **Payload Example:** ```json { "identity_reference": "partner-customer-123", "status": "cancelled", "status_date": "2026-03-05 19:53:08", "internal_reason": "Account is blocked.", "external_reason": "Your account has been restricted from further transactions." } ``` | Field | Description | | :--- | :--- | | `identity_reference` | A unique customer identifier provided by the partner | | `status` | The specific account status. Enum: `cancelled`. | | `status_date` | ISO 8601 timestamp of the status update. | | `internal_reason` | Internal system message detailing the specific reason for the block. | | `external_reason` | The designated message to be displayed to the end customer via your UI. | --- ## Retry behaviour The webhook retry mechanism ensures reliable delivery of event notifications. If your endpoint does not respond with `200 OK`, Banxa will automatically retry delivery. The payload is unchanged across all retry attempts. Retries follow a **Fibonacci sequence**: 1s, 2s, 3s, 5s, 8s, 13s, and so on. This continues for a **maximum of 2 hours**, with up to **18 retries** over that period. Respond with `200 OK` immediately and process the event asynchronously to avoid unnecessary retries. We recommend implementing idempotent webhook handling. In rare cases your endpoint may receive the same event more than once. Using `order_id` and `status` as a deduplication key will prevent duplicate processing. --- ## Securing Webhooks Banxa signs every webhook it sends using HMAC-SHA256. You verify this signature to confirm the request genuinely came from Banxa. > **Webhook verification is the reverse of request signing.** When you sign outbound API requests, you use a Banxa API path in the canonical string. When you verify an incoming webhook, you use the URI path of **your own webhook endpoint** — not a Banxa API path. Everything else is the same algorithm. Each webhook includes an `Authorization` header: ``` Authorization: Bearer {API_KEY}:{SIGNATURE}:{NONCE} ``` Banxa constructs the signature from: ``` POST\nYOUR_WEBHOOK_PATH\nNONCE\nPAYLOAD ``` Where `YOUR_WEBHOOK_PATH` is the path of the endpoint Banxa is calling — for example, `/webhooks/banxa` — not any Banxa API path. ### Verification flow 1. Extract the `Authorization` header from the incoming request 2. Strip the `Bearer ` prefix and split on `:` to get `receivedKey`, `receivedSignature`, `nonce` 3. Recompute the expected signature using the same algorithm, your secret, and `YOUR_WEBHOOK_PATH` 4. Use a **timing-safe comparison** to check that `receivedSignature` matches — never use `==` ### Code examples {% code-group %} ```python {% title="Python" %} import hmac MY_WEBHOOK_PATH = '/webhooks/banxa' KEY = '[YOUR_API_KEY]' SECRET = '[YOUR_API_SECRET]' def verify_webhook(auth_header, request_body): _, token = auth_header.split('Bearer ', 1) received_key, received_signature, nonce = token.split(':') data = f"POST\n{MY_WEBHOOK_PATH}\n{nonce}\n{request_body}" expected = hmac.new(SECRET.encode('utf-8'), data.encode('utf-8'), 'sha256').hexdigest() return hmac.compare_digest(received_signature, expected) ``` ```javascript {% title="Node.js" %} const crypto = require('crypto'); const MY_WEBHOOK_PATH = '/webhooks/banxa'; const KEY = '[YOUR_API_KEY]'; const SECRET = '[YOUR_API_SECRET]'; function verifyWebhook(authHeader, requestBody) { const token = authHeader.replace('Bearer ', ''); const [receivedKey, receivedSignature, nonce] = token.split(':'); const data = `POST\n${MY_WEBHOOK_PATH}\n${nonce}\n${requestBody}`; const expected = crypto.createHmac('sha256', SECRET).update(data).digest('hex'); return crypto.timingSafeEqual(Buffer.from(receivedSignature), Buffer.from(expected)); } ``` ```php {% title="PHP" %} $MY_WEBHOOK_PATH = '/webhooks/banxa'; $KEY = '[YOUR_API_KEY]'; $SECRET = '[YOUR_API_SECRET]'; function verifyWebhook($authHeader, $requestBody, $myWebhookPath, $key, $secret) { $token = str_replace('Bearer ', '', $authHeader); [$receivedKey, $receivedSignature, $nonce] = explode(':', $token); $data = implode("\n", ['POST', $myWebhookPath, $nonce, $requestBody]); $expected = hash_hmac('sha256', $data, $secret); return hash_equals($expected, $receivedSignature); } ``` ```java {% title="Java" %} import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.security.MessageDigest; import java.util.Formatter; private static final String MY_WEBHOOK_PATH = "/webhooks/banxa"; private static final String SECRET = "[YOUR_API_SECRET]"; public boolean verifyWebhook(String authHeader, String requestBody) throws Exception { String token = authHeader.replace("Bearer ", ""); String[] parts = token.split(":"); String nonce = parts[2]; String receivedSignature = parts[1]; String data = "POST\n" + MY_WEBHOOK_PATH + "\n" + nonce + "\n" + requestBody; SecretKeySpec signingKey = new SecretKeySpec(SECRET.getBytes(), "HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(signingKey); Formatter formatter = new Formatter(); for (byte b : mac.doFinal(data.getBytes())) formatter.format("%02x", b); String expected = formatter.toString(); return MessageDigest.isEqual(receivedSignature.getBytes(), expected.getBytes()); } ``` ```swift {% title="Swift" %} import CryptoKit let MY_WEBHOOK_PATH = "/webhooks/banxa" let SECRET = "[YOUR_API_SECRET]" func verifyWebhook(authHeader: String, requestBody: String) -> Bool { let token = authHeader.replacingOccurrences(of: "Bearer ", with: "") let parts = token.split(separator: ":") guard parts.count == 3 else { return false } let nonce = String(parts[2]) let receivedSignature = String(parts[1]) let data = "POST\n\(MY_WEBHOOK_PATH)\n\(nonce)\n\(requestBody)" let secretKey = SymmetricKey(data: SECRET.data(using: .utf8)!) let expected = HMAC.authenticationCode(for: data.data(using: .utf8)!, using: secretKey) .map { String(format: "%02hhx", $0) }.joined() return receivedSignature == expected } ``` ```ruby {% title="Ruby" %} require 'openssl' MY_WEBHOOK_PATH = '/webhooks/banxa' SECRET = '[YOUR_API_SECRET]' def verify_webhook(auth_header, request_body) token = auth_header.sub('Bearer ', '') received_key, received_signature, nonce = token.split(':') data = "POST\n#{MY_WEBHOOK_PATH}\n#{nonce}\n#{request_body}" expected = OpenSSL::HMAC.hexdigest('sha256', SECRET, data) ActiveSupport::SecurityUtils.secure_compare(received_signature, expected) end ``` {% /code-group %} --- Source: https://docs.banxa.com/products/hosted-checkout/docs/transaction-lifecycle/order-lookup.md --- title: "Order Lookup — GET /v2/orders | Banxa Docs" description: "Retrieve Banxa order details and status with GET /v2/orders and GET /v2/orders/{orderId}. Filter by date range, status, or externalCustomerId with examples." --- # Order Lookup Retrieve order details and status programmatically. Use this to check individual order status, build order history views, or reconcile transactions. --- ## List orders Retrieve a list of orders for your partner account, with optional filtering. ``` GET /{partnerRef}/v2/orders ``` ### Query parameters | Parameter | Required | Type | Description | |---|---|---|---| | `start` | Yes | string | Start date filter (ISO 8601 date, e.g., `2024-03-01`). | | `end` | Yes | string | End date filter (ISO 8601 date, e.g., `2024-03-31`). | | `status` | No | string | Filter by order status (e.g., `complete`, `expired`, `pending`). See [Order Statuses](./order-statuses.md). | | `externalCustomerId` | No | string | Filter orders for a specific customer by your internal ID. | | `page` | No | string | Page number for pagination. | | `limit` | No | string | Number of results per page. | | `skip` | No | string | Number of records to skip. | ### Example ```bash curl -X GET "https://api.banxa-sandbox.com/{partnerRef}/v2/orders" \ -H "x-api-key: YOUR_API_KEY" \ -G \ --data-urlencode "start=2024-03-01" \ --data-urlencode "end=2024-03-31" \ --data-urlencode "status=complete" ``` ### Response ```json { "orders": [ { "id": "191fa5b4b1f45e1cf784422e09317d56", "externalId": "a4b427ccb872a1744b317456bd0d165f", "externalCustomerId": "testing123", "country": "US", "orderType": "BUY", "orderStatusUrl": "https://banxa.status/a4b427ccb872a1744b317456bd0d165f", "status": "complete", "createdAt": "2022-08-22T09:10:43.724Z", "updatedAt": "2022-08-22T09:13:56.165Z", "crypto": { "id": "USDC", "blockchain": "TRON", "address": "0x0000000000000000000000000000000000000000", "network": "1" }, "fiat": "CAD", "fiatAmount": "1000.00", "cryptoAmount": "696.63", "paymentMethodId": "aabkd8792kd", "paymentMethodName": "debit-credit-card", "processingFee": "0.30", "networkFee": "1.67", "partnerFee": "", "walletAddress": "0x75b8d4d81377d4b0f11798779563462264914a24", "walletAddressTag": "1651352", "transactionHash": "0x5ea06c4724e8119704a1b57c918acf31742eb06cdf3f8678fdb17f41bbaf968e", "metadata": "{'tracking_id': 'HSKJDGHKSJG0393LJKJF'}" } ], "pageTotal": 10, "total": 2654 } ``` | Field | Description | |---|---| | `orders` | Array of order objects. | | `pageTotal` | Number of orders on this page. | | `total` | Total number of orders matching the query. | | `fiat` | Fiat currency code (string). | | `crypto` | Nested object with `id`, `blockchain`, `address`, `network`. | --- ## Get a specific order Retrieve full details for a single order by ID. ``` GET /{partnerRef}/v2/orders/{orderId} ``` ### Path parameters | Parameter | Description | |---|---| | `orderId` | The Banxa order ID. | ### Example ```bash curl -X GET "https://api.banxa-sandbox.com/{partnerRef}/v2/orders/191fa5b4b1f45e1cf784422e09317d56" \ -H "x-api-key: YOUR_API_KEY" ``` ### Response ```json { "id": "191fa5b4b1f45e1cf784422e09317d56", "externalId": "a4b427ccb872a1744b317456bd0d165f", "externalCustomerId": "testing123", "country": "US", "orderType": "BUY", "orderStatusUrl": "https://banxa.status/a4b427ccb872a1744b317456bd0d165f", "status": "complete", "createdAt": "2022-08-22T09:10:43.724Z", "updatedAt": "2022-08-22T09:13:56.165Z", "crypto": { "id": "USDC", "blockchain": "TRON", "address": "0x0000000000000000000000000000000000000000", "network": "1" }, "fiat": "CAD", "fiatAmount": "1000.00", "cryptoAmount": "696.63", "paymentMethodId": "apple-pay", "paymentMethodName": "Apple Pay", "processingFee": "0.30", "networkFee": "1.67", "walletAddress": "0x75b8d4d81377d4b0f11798779563462264914a24", "walletAddressTag": "1651352", "transactionHash": "0x5ea06c4724e8119704a1b57c918acf31742eb06cdf3f8678fdb17f41bbaf968e", "metadata": "{'tracking_id': 'HSKJDGHKSJG0393LJKJF'}" } ``` --- ## Error responses | Status | Description | |---|---| | `400 Bad Request` | Invalid parameter format. Response includes an error message describing the issue. | | `404 Not Found` | Order not found. | --- Source: https://docs.banxa.com/products/hosted-checkout/docs/on-ramp-off-ramp/custodial-vs-non-custodial.md --- title: "Custodial vs. Non-Custodial Off-Ramp | Banxa Docs" description: "Choose how crypto transfers work in Banxa's sell flow. Custodial requires POST /v2/orders/{id}/confirm after your platform executes the blockchain transfer." --- # Custodial vs. Non-Custodial Off-Ramp For the off-ramp (sell) flow, there are two ways to handle the crypto transfer from the customer to Banxa. The right choice depends on whether your platform holds custody of your customers' crypto. ```mermaid flowchart TD A([Sell order initiated]) --> B[POST /v2/sell] B --> C[Customer completes checkout
KYC + bank account details] C --> D{Custodial?} D -->|No| E[Banxa shows deposit
address + QR code] E --> F[Customer sends crypto
from their own wallet] F --> Z D -->|Yes| G{Notification method} G -->|Deeplink| H[Banxa redirects to partner
with deposit address in deeplink] H --> I[Partner transfers crypto
to Banxa deposit address] I --> Z G -->|Webhook| J[Order status → waitingPayment
Webhook fires to partner endpoint] J --> K[Partner calls GET /v2/orders/:id
retrieves deposit address] K --> L[Partner transfers crypto
to Banxa deposit address] L --> Z Z[Banxa detects on-chain transfer] --> M([Fiat payout released to customer]) ``` --- ## Non-custodial flow The customer initiates the crypto transfer themselves. Banxa displays a receiving wallet address and QR code during checkout, and the customer sends the crypto directly from their own wallet. **How it works:** 1. Customer creates a sell order and completes checkout (KYC, bank account details). 2. Banxa displays a deposit address and QR code. 3. Customer transfers the crypto from their wallet to the Banxa deposit address. 4. Banxa detects the incoming transfer on-chain and processes the fiat payout. **No additional API calls are required from your platform** after order creation. **Best for:** - Self-custody wallets where the customer controls their private keys. - Consumer wallet applications. --- ## Custodial flow Your platform holds custody of the customer's crypto and executes the blockchain transfer on their behalf. After the customer completes checkout, Banxa needs to notify your platform of the deposit address. There are two ways this works: ### Option 1: Deeplink 1. Customer creates a sell order and completes checkout (KYC, bank account details). 2. Banxa redirects back to your platform via a deeplink that includes the deposit address and all required transfer parameters. 3. Your platform reads the deeplink parameters and executes the blockchain transfer to Banxa's deposit address. 4. Banxa detects the incoming transfer on-chain and processes the fiat payout. **Example deeplink:** ``` https://example.com/banxa/?blockchain=ETH&coinAmount=39.29&fiatAmount=55 &fulfillmentStatus=Pending&providerWalletAddress=0x03EA4c7ED1d8199510017D88f2783Ef43ab5a4fd &coinType=USDT&orderId=7dbb7f5c2ad8bbbd022e4426fe98fb3a&identityStatus=Complete &orderRef=7dbb7f5c2ad8bbbd022e4426fe98fb3a&paymentStatus=Action%2BRequired &coin=USDT&orderStatus=pending&fiat=AUD ``` Your platform uses `providerWalletAddress` as the destination address for the blockchain transfer, and `coinAmount` as the amount to send. ### Option 2: Webhook 1. Customer creates a sell order and completes checkout (KYC, bank account details). 2. Banxa updates the order status to `waitingPayment` and triggers a webhook to your endpoint. 3. Your platform calls `GET /v2/orders/{id}` to retrieve the deposit address and transfer details. 4. Your platform executes the blockchain transfer to Banxa's deposit address. 5. Banxa detects the incoming transfer on-chain and processes the fiat payout. Contact Banxa to discuss which option is best for your integration. **Best for:** - Exchanges or custodial wallet products where your platform manages customer funds. - Cases where you want to automate the crypto transfer without customer action. --- ## Choosing a flow Contact Banxa to configure your preferred flow. The flow is set at the partner account level and applies to all sell orders for your integration. | Consideration | Non-custodial | Custodial | |---|---|---| | You hold customer crypto | No | Yes | | Customer action required for transfer | Yes | No | | Webhook integration required | Optional | Yes | --- Source: https://docs.banxa.com/products/hosted-checkout/docs/on-ramp-off-ramp/on-ramp-overview.md --- title: "Crypto On-Ramp Integration Overview | Banxa Docs" description: "How Banxa's fiat-to-crypto on-ramp works: payment processing, KYC/AML compliance, liquidity handling, and supported currencies and payment methods by region." --- # On-Ramp Overview On-ramp refers to the process of converting fiat currency into cryptocurrency. Banxa acts as the payment processor and crypto exchange, handling payments, compliance, and crypto delivery on your behalf. --- ## How it works ```mermaid flowchart TD A([Customer initiates buy]) --> B["GET /v2/quotes/ONRAMP
Fetch live quote"] B --> C["POST /v2/buy
Create order, returns checkoutUrl"] C --> D[Redirect or embed checkoutUrl] D --> E[Customer completes checkout
KYC + payment] E --> F[Banxa processes payment
and delivers crypto to wallet] F --> G[Customer redirected to redirectUrl] G --> H([Webhook: order status updates]) ``` 1. Your customer selects a crypto asset and enters the amount they want to spend in their local currency. 2. They complete identity verification (KYC) if required. 3. They pay using their preferred payment method (card, bank transfer, Apple Pay, etc.). 4. Banxa processes the payment and delivers the purchased crypto to the customer's wallet address. The customer's wallet address is provided at order creation — either by you (via the API or referral URL) or by the customer in the checkout flow. --- ## What Banxa handles | Responsibility | Details | |---|---| | Payment processing | Card, bank transfers, Apple Pay, Google Pay, and local payment methods | | KYC / AML compliance | Identity verification, document collection, sanctions screening | | Crypto liquidity | Exchange rate sourcing and crypto delivery | | Regulatory coverage | Licences and compliance obligations in supported jurisdictions | | Customer support | Transaction disputes, payment issues, and KYC queries | --- ## Supported payment methods Payment method availability depends on the customer's country and your partner configuration. Retrieve the full list of available methods using: ``` GET /{partnerRef}/v2/payment-methods ``` Common methods include: debit/credit card, Apple Pay, Google Pay, SEPA, GBP bank transfer, ACH, PayID, UPI, PIX, and PayPal. --- ## Supported currencies and crypto Retrieve the full lists from the API: ``` GET /{partnerRef}/v2/fiats → supported fiat currencies GET /{partnerRef}/v2/crypto → supported cryptocurrencies GET /{partnerRef}/v2/countries → supported countries ``` --- ## Integration → [API Integration: Create Buy Order](../api-integration/create-buy-order.md) → [Referral Integration](../referral-integration/overview.md) --- Source: https://docs.banxa.com/products/hosted-checkout/docs/on-ramp-off-ramp/off-ramp-overview.md --- title: "Crypto Off-Ramp Integration Overview | Banxa Docs" description: "How Banxa's crypto-to-fiat off-ramp works. Covers custodial vs. non-custodial flows, supported fiat payout methods, and required partner account configuration." --- # Off-Ramp Overview Off-ramp refers to the process of converting cryptocurrency into fiat currency. The customer sends crypto to Banxa, and Banxa delivers the fiat payout to the customer's bank account or payment method. --- ## How it works 1. Your customer selects the crypto they want to sell and the amount. 2. They provide their bank account or payout details. 3. They complete identity verification (KYC) if required. 4. The crypto is transferred to Banxa's receiving wallet — either by the customer (non-custodial) or by your platform (custodial). 5. Banxa verifies receipt of the crypto and releases the fiat payout to the customer. --- ## Custodial vs. non-custodial The sell flow has two variants depending on how the crypto transfer is executed: | | Non-custodial | Custodial | |---|---|---| | Who transfers the crypto | Customer | Your platform | | How | Customer scans QR code or sends manually | Your platform executes transfer via blockchain | | Typical use case | Consumer wallets | Exchange or custodial wallet products | → See [Custodial vs. Non-Custodial](./custodial-vs-non-custodial.md) for implementation details. --- ## Supported payout methods Fiat payout methods vary by region. Common options include: - **PayID** — AUD (Australia) - **SEPA bank transfer** — EUR (Europe) - **PIX** — BRL (Brazil) Retrieve the available options for your integration: ``` GET /{partnerRef}/v2/payment-methods ``` --- ## Off-ramp configuration Off-ramp must be enabled and configured for your partner account before you can create sell orders. Contact Banxa to: - Enable off-ramp for your account. - Choose between custodial and non-custodial flow. - Configure supported crypto assets and payout methods. --- ## Integration → [API Integration: Create Sell Order](../api-integration/create-sell-order.md) --- Source: https://docs.banxa.com/products/hosted-checkout/docs/identity-compliance/kyc-sharing.md --- title: "KYC Sharing via Sumsub | Banxa Docs" description: "Share Sumsub KYC verification data with Banxa before checkout using POST /v2/identities/token/share. Requires HMAC auth. Full request body reference." --- # KYC Sharing If your platform uses Sumsub for identity verification, you can share the customer's verified KYC data with Banxa before they enter checkout. Banxa retrieves the data directly from Sumsub — reducing or eliminating the verification steps the customer needs to complete in the Banxa checkout. --- ## Prerequisites - Your platform uses Sumsub for identity verification. - The identity endpoint must be enabled for your account (requires Banxa approval). - You have completed the Sumsub token sharing agreement with Banxa. - Only share data for customers who have **already been KYC verified** on your platform. --- ## How it works ```mermaid sequenceDiagram participant P as Partner Platform participant B as Banxa Identity API participant S as Sumsub participant C as Customer Note over P,C: At KYC verification time (recommended) C->>P: Completes KYC on partner platform P->>S: Generate share token
(clientId: banxa.com_5335) S-->>P: Share token P->>B: POST /v2/identities/token/share
{externalCustomerId, provider.token, ...} B-->>P: 202 Accepted Note over P,C: When customer initiates a transaction C->>P: Initiates buy or sell P->>B: POST /v2/buy or /v2/sell B-->>P: checkoutUrl P->>C: Redirect to checkoutUrl C->>B: Enters Banxa checkout B->>S: Retrieve KYC data using stored token S-->>B: Customer KYC data B->>C: Checkout with KYC pre-filled ``` 1. When a customer is KYC-verified on your platform, generate a Sumsub share token using Banxa's `clientId` and call the Banxa identity endpoint. Do this at verification time — not at order creation, as the data may not be processed in time for the customer's session. 2. Banxa stores the token and associates it with the `externalCustomerId`. 3. When the customer enters the Banxa checkout, Banxa retrieves their KYC data from Sumsub. Their details are pre-populated and some verification steps may be skipped. 4. Banxa still performs its own KYC due diligence. Additional information may be requested if required by compliance processes. → See [Sumsub Integration](./sumsub-integration.md) for how to generate the share token and for details on Copy Applicant, which extracts a fuller profile including address and TIN. --- ## Endpoint ``` POST /{partnerRef}/v2/identities/token/share ``` This endpoint uses **HMAC authentication**, not the standard `x-api-key` header. HMAC signing must be done server-side — never embed your API secret in frontend or mobile code. See [Authentication & Environments](../getting-started/authentication-and-environments.md) for the signing algorithm, code examples, and error codes. --- ## Request body | Field | Type | Required | Description | |---|---|---|---| | `externalCustomerId` | string | Yes | Your stable customer identifier. Must match the value used in order creation. | | `email` | string | Yes | Customer's email address. | | `mobileNumber` | string | Yes | Customer's mobile number including country code (e.g., `+61412345678`). | | `provider.vendor` | string | Yes | Must be `sumsub`. | | `provider.token` | string | Yes | The Sumsub share token generated using `clientId: banxa.com_5335`. | --- ## Example request ```bash curl -X POST "https://api.banxa-sandbox.com/{partnerRef}/v2/identities/token/share" \ -H "Authorization: Bearer API_KEY:SIGNATURE:NONCE" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "externalCustomerId": "user_12345", "mobileNumber": "+61412345678", "email": "customer@example.com", "provider": { "vendor": "sumsub", "token": "_act-sbx-jwt-eyJhbGciOiJub25lIn0..." } }' ``` --- ## Response **202 Accepted** ```json { "externalCustomerId": "user_12345" } ``` A `202` response confirms Banxa has received and queued the identity data for processing. It does not guarantee immediate availability in the checkout. --- ## Important notes - `externalCustomerId` must be the same value used in order creation. Using different values for the same customer creates separate identities in the Banxa system. - Banxa may still collect additional information from the customer during checkout if required by compliance processes. - Only submit data for verified customers. Do not pass unverified or provisional data. --- Source: https://docs.banxa.com/products/hosted-checkout/docs/identity-compliance/overview.md --- title: "KYC & Compliance Integration Overview | Banxa Docs" description: "Banxa handles KYC and AML compliance automatically in checkout. Optionally share verified customer identity to reduce friction and improve conversion rates." --- # Identity & Compliance — Overview Banxa is a regulated financial services provider. Every customer who transacts through Banxa must pass identity verification (KYC) and comply with anti-money laundering (AML) requirements. This happens automatically within the Banxa checkout flow. As an integration partner, you do not need to manage KYC — Banxa handles all identity verification, document collection, and regulatory compliance. However, there are optional ways to streamline the customer experience if you already collect identity information. --- ## How KYC works in the checkout When a customer enters the Banxa checkout for the first time, they will be asked to provide: - Personal details (name, date of birth, address) - Identity document (passport, driver's licence, or national ID) - In some cases, a liveness check (selfie video) For returning customers, Banxa recognises them and may not require re-verification, depending on the transaction amount and jurisdiction. --- ## Verification levels The level of verification required depends on the transaction amount and the customer's jurisdiction. Higher-value transactions require more thorough verification. Thresholds vary by jurisdiction and are not published — contact Banxa for details applicable to your markets. → See [Verification Flow](./verification-flow-and-tiers.md) for details on how the flow works. --- ## Streamlining KYC for your customers If your platform already collects and verifies customer identity, you can share that information with Banxa to reduce or eliminate the KYC steps your customers need to complete in the checkout. ```mermaid flowchart TD A{Does your platform\nverify customer identity?} -->|No| B[Banxa handles all KYC\nin checkout — no action needed] A -->|Yes| C{Which KYC\nprovider?} C -->|Sumsub| D[Sumsub token sharing\nSee Sumsub Integration] C -->|Other or in-house| E[KYC data sharing\nor contact Banxa] D --> F([Customer may skip KYC\nin Banxa checkout]) E --> F B --> G([Customer completes KYC\nin Banxa checkout]) ``` There are two ways to do this: ### 1. KYC data sharing Pass customer identity data (name, address, document details) to Banxa before creating an order. This reduces the information the customer needs to re-enter. ### 2. Sumsub token sharing If your platform uses Sumsub for KYC, you can pass a Sumsub share token to Banxa. Banxa retrieves the customer's verification data directly from Sumsub — the customer may be able to skip KYC entirely. → See [KYC Sharing](./kyc-sharing.md) and [Sumsub Integration](./sumsub-integration.md). --- ## Prerequisites for KYC sharing KYC sharing must be enabled for your partner account before you can use the identity endpoint. Contact Banxa to: 1. Complete the Banxa questionnaire to confirm solution fit. 2. Receive approval and activation of the identity endpoint. 3. If using Sumsub: complete the token sharing agreement. --- ## Customer transparency When using KYC sharing, inform your customers that their personal details and KYC documents will be shared with Banxa. This prevents confusion if Banxa needs to contact the customer for additional documentation. --- Source: https://docs.banxa.com/products/hosted-checkout/docs/identity-compliance/verification-flow-and-tiers.md --- title: "KYC Verification Flow & Tiers | Banxa Docs" description: "How Banxa verifies customer identity: document upload, liveness checks, KYC tiers by transaction value, returning customer recognition, and KYC sharing impact." --- # Verification Flow & KYC Tiers --- ## The customer verification flow When a customer enters the Banxa checkout, the level of identity verification required is determined by the transaction amount, jurisdiction, and their history with Banxa. ### First-time customers A first-time customer will typically be asked to: 1. **Provide personal details** — full name, date of birth, residential address. 2. **Upload an identity document** — passport, driver's licence, or national ID (front and back where applicable). 3. **Complete a liveness check** — a short selfie video to confirm the document matches the person. This step uses Sumsub and requires camera access. ### Returning customers Banxa recognises returning customers by their email address or `externalCustomerId`. Depending on their verification status and the transaction amount, returning customers may: - Proceed directly to payment (fully verified, below tier threshold). - Be asked to provide additional documents for higher-value transactions. - Be asked to re-verify if their documents have expired or if compliance requirements have changed. --- ## Verification levels Banxa applies risk-based verification thresholds. The level of documentation required increases with transaction value and cumulative volume — higher-value transactions require more thorough verification. Thresholds vary by jurisdiction and are not published. Contact Banxa for the thresholds applicable to your customers' regions. --- ## KYC sharing and its effect on the flow If you use [KYC sharing](./kyc-sharing.md) or [Sumsub integration](./sumsub-integration.md), Banxa may be able to pre-fill or skip steps in the verification flow: - **KYC data sharing** — personal details are pre-populated. The customer may still need to confirm or upload documents depending on the tier. - **Sumsub token sharing** — if the customer is fully verified via Sumsub, they may skip KYC entirely for transactions within the applicable tier. Banxa still conducts its own due diligence regardless of what is shared. Additional steps may be required. --- ## Camera and document capture The KYC flow requires: - **Camera access** for document capture and liveness check. - **Video playback** for animated instructions shown during document capture. On mobile, ensure your WebView or browser component is configured to allow these. → See [Integration Best Practices](../getting-started/integration-best-practices.md#webview-requirements) and [Embedded Checkout — Mobile](../checkout-experience/iframe/webview-mobile.md). --- ## Compliance responsibilities Banxa is the regulated entity responsible for KYC and AML compliance. As a partner, you are not required to conduct independent compliance checks on Banxa-processed transactions. However, you are responsible for: - Ensuring that your use of KYC sharing complies with applicable data protection laws in your jurisdiction. - Informing customers that their data will be shared with Banxa when using the identity endpoint. --- Source: https://docs.banxa.com/products/hosted-checkout/docs/identity-compliance/sumsub-integration.md --- title: "Sumsub Integration | Banxa Docs" description: "Two approaches for sharing Sumsub KYC data with Banxa: Reusable KYC (token sharing) and Copy Applicant. Understand the data coverage difference and setup for each." --- # Sumsub Integration Banxa supports two approaches for sharing Sumsub KYC data. They differ in how much of the customer's verified profile Banxa can retrieve. | | Reusable KYC (default) | Copy Applicant | | :--- | :--- | :--- | | Data Banxa retrieves | Name, DOB, selfie, document | Name, DOB, selfie, document, address, TIN | | Sumsub product | Reusable KYC | Copy Applicant (separate Sumsub product) | | Setup | Add Banxa as a recipient in Sumsub (Donors tab) | Sign Copy Applicant agreement with Sumsub; contact Banxa to configure | | Configured by | Partner | Banxa (per partner account) | --- ## Reusable KYC Banxa retrieves the customer's basic verification data from Sumsub using a share token. Any fields not retrieved can be collected separately — Banxa's checkout will prompt the customer for any outstanding information during the flow. ### Setup Reusable KYC uses Sumsub's [Reusable KYC via API](https://docs.sumsub.com/docs/reusable-kyc-via-api) feature. No additional Sumsub agreement is required. Sumsub setup (done once per partner): 1. In your Sumsub dashboard, open the **Partners** page and go to the **Donors** tab. 2. Click **Add Donor**. 3. Copy your Partner Token and share it with Banxa. Banxa will add you as a recipient. No further action is required on your side for registration. ### Generating a share token When a customer passes KYC on your platform, generate a share token using Banxa's `clientId`: ```javascript const response = await fetch('https://api.sumsub.com/resources/accessTokens/-/shareToken', { method: 'POST', headers: { 'X-App-Token': YOUR_SUMSUB_APP_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ userId: customerSumsubApplicantId, clientId: 'banxa.com_5335' // Must be Banxa's clientId }) }); const { token } = await response.json(); ``` You **must** use Banxa's `clientId` when generating the token. Using a different `clientId` will block Banxa from accessing the token. ``` banxa.com_5335 ``` → Refer to [Sumsub documentation](https://docs.sumsub.com) for the authoritative share token API reference. ### Sending the token to Banxa Pass the generated token to the Banxa identity endpoint. See [KYC Sharing](./kyc-sharing.md) for the full endpoint reference and request body. ```bash curl -X POST "https://api.banxa-sandbox.com/{partnerRef}/v2/identities/token/share" \ -H "Authorization: Bearer API_KEY:SIGNATURE:NONCE" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "externalCustomerId": "user_12345", "mobileNumber": "+61412345678", "email": "customer@example.com", "provider": { "vendor": "sumsub", "token": "_act-sbx-jwt-eyJhbGciOiJub25lIn0..." } }' ``` Call this at KYC verification time — not at order creation. If called at order creation, the data may not be processed in time for the customer's checkout session. ### Token expiry The Sumsub share token has an expiry. Generate a fresh token close to the time you send it to Banxa. --- ## Copy Applicant Banxa copies the customer's applicant record directly from your Sumsub account. Copy Applicant is a separate Sumsub product — see [Sumsub Copy Applicant](https://docs.sumsub.com/docs/copy-applicant) for Sumsub's documentation. ### Data coverage Banxa currently retrieves: name, DOB, selfie, document, address, and TIN. Proof of address and other fields are not currently extracted — any outstanding requirements can be supplemented via the identity endpoint if needed. What Banxa can retrieve depends on what your KYC flow collected. Fields not present in your Sumsub applicant record will not be available regardless of which approach is used. ### Setup 1. **Sign the Copy Applicant agreement with Sumsub** — Copy Applicant is a separate Sumsub product. Contact your Sumsub account manager to enable it for your account. 2. **Contact Banxa to configure Copy Applicant on your partner account** — no code changes are required on your side once setup is complete. Banxa retrieves the applicant data automatically when the customer enters checkout. --- ## iOS liveness check On iOS, KYC liveness checks require `SFSafariViewController` — they will not complete in a standard `WKWebView` even if the customer's Sumsub data has been shared. This applies regardless of which sharing approach you use. --- Source: https://docs.banxa.com/products/hosted-checkout/docs/identity-compliance/global-kyc-framework.md --- title: Global KYC Framework description: KYC tiers, transaction limits, and data collection requirements by region. --- # Global KYC Framework Banxa applies a tiered KYC model across all supported regions. The required verification tier for a given transaction is determined dynamically by eligibility checks, based on transaction amount, payment method, and jurisdiction. Partners do not control tier assignment. Verification requirements escalate progressively — previously submitted information is retained and not re-collected. --- ## KYC tiers | KYC Flow | Definition | | :--- | :--- | | Express | All payment methods. Note for AP&GP, we collect data from the payment wallet to reduce the user entered data. Minimal user data (2–4 fields). No ID documents or liveness check required. | | Standard | All payment methods. Standard onboarding requiring identity and liveness verification. | | Additional | All payment methods. Standard onboarding plus additional verification (e.g. source of funds). | --- ## Europe | KYC Flow | Order Limit (€) | Threshold | Collected info | | :--- | :--- | :--- | :--- | | Express | 0–500 | 1st transaction only | Name, Address, DOB, TIN | | Standard (T1) | 501–9,000 | — | Name, Address, DOB, TIN. Document, Selfie, Purpose of txn | | Additional (T2) | >9,000 | — | Name, Address, DOB, TIN. Document, Selfie, Purpose of txn. Source of funds | > Countries: AT, BE, BG, CY, CZ, DE, DK, EE, ES, FI, FR, GR, HU, HR, IE, IT, LV, LT, LU, MT, NL, PL, PT, RO, SK, SI, SE --- ## United States | KYC Flow | Order Limit ($) | Threshold | Collected info | | :--- | :--- | :--- | :--- | | Express | 0–250 | Up to 250/week, 1,000/month, 12,000/year | Name, Address | | Standard | 251–15,000 | — | Name, Address, DOB. Document, Selfie, Purpose of txn, Occupation, SSN | | Additional | >15,000 | — | Name, Address, DOB. Document, Selfie, Purpose of txn, Occupation, SSN. Source of funds | > States: AK, AL, AR, AZ, CA, CO, CT, DC, DE, FL, GA, HI, IA, ID, IL, IN, KS, KY, MA, MD, ME, MI, MN, MO, MS, MT, NC, ND, NE, NH, NJ, NM, NV, OH, OK, OR, RI, SC, SD, TN, UT, VA, VT, WV, WY --- ## Canada | KYC Flow | Order Limit ($) | Threshold | Collected info | | :--- | :--- | :--- | :--- | | Express | 0–600 | Up to total of 600, max 5 transactions | Address, DOB | | Standard | 601–15,000 | — | Name, Address, DOB. Document, Selfie, Purpose of txn, Occupation | | Additional | >15,000 | — | Name, Address, DOB. Document, Selfie, Purpose of txn, Occupation. Source of funds | --- ## Great Britain | KYC Flow | Order Limit (£) | Collected info | | :--- | :--- | :--- | | Standard | 0–8,000 | Name, Address, DOB. Document, Selfie, Purpose of txn, Occupation. Questionnaire, Category, Cooling off | | Standard + POA | 8,001–15,000 | Name, Address, DOB. Document, Selfie, Purpose of txn, Occupation. Questionnaire, Category, Cooling off. Proof of address | | Additional | >15,000 | Name, Address, DOB. Document, Selfie, Purpose of txn, Occupation. Questionnaire, Category, Cooling off. Proof of address. Source of funds | --- ## Selected Regions | KYC Flow | Order Limit ($) | Threshold | Collected info | | :--- | :--- | :--- | :--- | | Express | 0–500 | 1st transaction only | Name, Address | | Standard | 501–9,000 | — | Name, Address, DOB. Document, Selfie | | Additional | >9,000 | — | Name, Address, DOB. Document, Selfie. Source of funds | > Countries: AD, CH, CL, GI, GG, HK, IS, IM, JP, KR, LI, NZ, NO, SG, SM --- ## Australia | KYC Flow | Order Limit ($) | Collected info | | :--- | :--- | :--- | | Standard | 0–15,000 | Name, Address, DOB. Document, Selfie | | Additional | >15,000 | Name, Address, DOB. Document, Selfie. Source of funds | --- ## Rest of World | KYC Flow | Order Limit ($) | Collected info | | :--- | :--- | :--- | | Standard | 0–15,000 | Name, Address, DOB. Document, Selfie. Purpose of txn, Occupation | | Additional | >15,000 | Name, Address, DOB. Document, Selfie. Purpose of txn, Occupation. Source of funds | > All other supported countries not listed in the regional sections above. --- ## Summary tables ### Express tier by region | | US | Canada | Europe | Selected Regions | | :--- | :--- | :--- | :--- | :--- | | Customer details required | Full Name & Address | Full Name & Address | Full Name, DOB, TIN & Address | Full Name, DOB & Address | | Transaction Limit | 250 USD | 600 CAD | 500 EUR | 500 EUR | | Daily Limit | 250 USD | 600 CAD | 500 EUR | 500 EUR | | Weekly Limit | 250 USD | 600 CAD | 500 EUR | 500 EUR | | Monthly Limit | 1,000 USD | 600 CAD | 500 EUR | 500 EUR | | Annual Limit | 12,000 USD | 600 CAD | 500 EUR | 500 EUR | | Maximum Transactions | — | 5 | 1 | 1 | ### Standard tier by region | | US | Canada | Europe | Selected Regions | Rest of World | Australia | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | Customer details required | PD + Document + Liveness + Purpose of txn + Occupation | PD + Document + Liveness + Purpose of txn + Occupation | PD + Document + Liveness, DOB, TIN | PD + Document + Liveness, Selfie + DOB | PD + Document + Liveness | PD + Document + Liveness, Selfie + DOB + Source of funds | | Transaction Limit | 15,000 USD | 15,000 CAD | 15,000 EUR | 15,000 EUR | 15,000 AUD | 15,000 AUD | | Daily Limit | 15,000 USD | 15,000 CAD | 15,000 EUR | 15,000 EUR | 15,000 AUD | 15,000 AUD | | Weekly Limit | 30,000 USD | 30,000 CAD | 30,000 EUR | 30,000 EUR | 30,000 AUD | 15,000 AUD | | Monthly Limit | 100,000 USD | 100,000 CAD | 100,000 EUR | 100,000 EUR | 100,000 AUD | 15,000 AUD | | Annual Limit | 100,000 USD | 100,000 CAD | 100,000 EUR | 100,000 EUR | 100,000 AUD | 15,000 AUD | --- Source: https://docs.banxa.com/products/hosted-checkout/docs/referral-integration/overview.md --- title: "Banxa Referral Integration Overview | Banxa Docs" description: "Integrate Banxa with no backend required. Construct a partner URL with query parameters and redirect customers to the Banxa-hosted checkout flow." --- # Referral Integration Overview The Referral integration is the fastest way to embed Banxa into your product. You construct a URL with parameters, redirect your customer to it, and Banxa handles the entire checkout experience. There are no server-to-server API calls. No backend is required. --- ## How it works ```mermaid flowchart LR A([Customer ready\nto transact]) --> B[Partner constructs\nreferral URL] B --> C[Redirect customer\nto URL] C --> D[Banxa-hosted checkout\nKYC + payment] D --> E([Customer redirected\nto redirectUrl]) ``` 1. Banxa provides you with a base referral URL tied to your partner account. 2. You append query parameters to pre-populate the checkout state (wallet address, crypto, fiat, amount, etc.). 3. When a customer is ready to transact, redirect them to the constructed URL. 4. The customer completes their transaction on the Banxa-hosted checkout. 5. On completion, the customer is returned to your `redirectUrl`. You can display this checkout as a **full redirect** (new tab or same window) or within an **iFrame** embedded in your application. --- ## When to use Referral Choose Referral if: - You want to go live quickly with minimal backend work. - You don't need real-time order status notifications (webhooks). - You don't need to show a quote comparison within your own UI. - KYC sharing is not required. If any of those are requirements, use the [API integration](../api-integration/api-integration-overview.md) instead. --- ## Limitations | Feature | Referral | API | |---|---|---| | Webhooks | — | ✓ | | Quote UI in your app | — | ✓ | | KYC sharing | — | ✓ | | Order history lookup | — | ✓ | | Backend required | No | Yes | --- ## Next steps - [Constructing Referral URLs](./constructing-referral-urls.md) - [Supported Parameters](./supported-parameters.md) --- Source: https://docs.banxa.com/products/hosted-checkout/docs/referral-integration/constructing-referral-urls.md --- title: "Constructing Banxa Referral URLs | Banxa Docs" description: "Build Banxa referral URLs by appending query parameters to your partner subdomain. Covers sandbox and production URL formats with URL encoding guidance." --- # Constructing Referral URLs Banxa provides you with a base referral URL for your partner account. You construct a transaction-ready URL by appending query parameters. --- ## URL structure ``` https://{partnerRef}.banxa.com?{parameters} ``` Your `{partnerRef}` is your partner subdomain, provided by Banxa during onboarding. For sandbox, use: ``` https://{partnerRef}.banxa-sandbox.com?{parameters} ``` --- ## Example A referral URL pre-populated with a wallet address, crypto asset, and fiat currency: ``` https://yourpartner.banxa.com?walletAddress=0xe3BDEFdAeFF070925eB7FfC49F9B30c647Cb751e&coinType=ETH&fiatType=AUD&blockchain=ETH ``` Parameters are standard URL query string format. All parameters are optional — any value not supplied will default to the customer's previous selection or the checkout defaults configured in your Partner Dashboard. --- ## Encoding Always URL-encode parameter values, particularly wallet addresses and email addresses which may contain special characters. --- ## Choosing a checkout mode Once you have your referral URL, choose how to present it: - **Redirect** — open the URL in a new tab or the same window. - **iFrame / WebView** — load the URL inside an embedded component in your app. → See [Checkout Experience](../checkout-experience/redirect/redirect-overview.md) for implementation details. --- Source: https://docs.banxa.com/products/hosted-checkout/docs/referral-integration/supported-parameters.md --- title: "Referral URL Parameters Reference | Banxa Docs" description: "Full reference for Banxa referral URL query parameters: wallet address, crypto, fiat, amount, email, payment method ID, and externalCustomerId." --- # Supported Referral Parameters All parameters are optional. Any value not provided will default to customer preferences or the defaults configured in your Partner Dashboard. --- ## Parameters | Parameter | Type | Description | |---|---|---| | `walletAddress` | string | Customer's wallet address for the receiving crypto asset. Pre-populates the wallet field in checkout. | | `walletAddressTag` | string | Customer's wallet tag or memo. Required for transacting on certain blockchains such as BNB and XRP. | | `blockchain` | string | Blockchain network for the wallet address (e.g., `ETH`, `BTC`, `BNB`). Required when `walletAddress` is provided for multi-chain assets. | | `coinType` | string | Cryptocurrency to purchase (e.g., `ETH`, `BTC`, `USDT`). Pre-selects the crypto asset in checkout. | | `fiatType` | string | Fiat currency for the transaction (e.g., `AUD`, `USD`, `EUR`). Pre-selects the fiat currency. | | `fiatAmount` | number | Amount of fiat currency the customer wants to spend. Either `fiatAmount` or `coinAmount` can be provided, not both. | | `coinAmount` | number | Amount of cryptocurrency the customer wants to receive. Either `fiatAmount` or `coinAmount` can be provided, not both. | | `orderType` | string | Pass `sell` to open the checkout in off-ramp (sell) mode. Omit for on-ramp (buy). | | `email` | string | Customer's email address. Pre-populates the email field, reducing friction at checkout. | | `paymentMethodId` | string | Pre-selects a payment method. See [supported values](#payment-method-ids). | | `returnUrl` | string | URL to redirect the customer to after the transaction is complete or cancelled. Must be URL-encoded. Also accepted as `redirectUrl`. | | `externalCustomerId` | string | Your stable identifier for this customer. Used by Banxa to recognise returning customers and avoid repeat KYC collection. See [externalCustomerId best practices](../getting-started/integration-best-practices.md#externalcustomerid). | | `backgroundColor` | string | Hex colour value (without `#`) to override the checkout background colour, e.g. `ffffff`. | | `primaryColor` | string | Hex colour value for active buttons. | | `secondaryColor` | string | Hex colour value for button hover effects. | | `textColor` | string | Hex colour value for all text in the checkout flow. | | `theme` | string | `light` or `dark`. Sets contrast mode to match your background colour. | --- ## Payment method IDs | Value | Payment method | |---|---| | `debit-credit-card` | Debit or credit card | | `apple-pay` | Apple Pay | | `google-pay` | Google Pay | | `sepa-bank-transfer` | SEPA bank transfer (EUR) | | `gbp-bank-transfer` | Bank transfer (GBP) | | `ach-bank-transfer` | ACH bank transfer (USD) | | `payid-bank-transfer` | PayID bank transfer (AUD) | | `upi` | UPI (INR) | | `pix` | PIX (BRL) | | `paypal` | PayPal | Not all payment methods are available in all regions. Available methods are determined by your partner configuration and the customer's country. --- ## Sell (off-ramp) To open the checkout in sell mode, pass `orderType=sell`. All other parameters apply. Contact Banxa to confirm that off-ramp is enabled for your partner account. --- Source: https://docs.banxa.com/products/hosted-checkout/docs/referral-integration/javascript-sdk.md --- title: "Banxa JavaScript SDK for Referral Integration | Banxa Docs" description: "Embed or redirect to Banxa checkout using the JavaScript SDK. One-line script tag setup with redirect() and iframe() methods for referral integrations." --- # JavaScript SDK The Banxa JavaScript SDK provides a lightweight alternative to manually constructing referral URLs. Include a single script tag and use the `Banxa` class to trigger redirect or iFrame checkout from any button or event in your page. --- ## Include the SDK Add the following loader script to your page. It asynchronously loads the SDK and calls your `yourOnLoadFunction` callback when ready. ```javascript !function(callback){ var b = document.createElement('script'); b.type = "text/javascript"; b.async = true; b.src = "https://sdk.banxa.com/js/banxa-sdk-latest.js"; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(b, x); if (callback) { b.addEventListener("load", function() { callback() }) } }(yourOnLoadFunction); ``` --- ## Initialise the Banxa class Create an instance of `Banxa` with your partner name. Use the second argument to select sandbox or production. ```javascript // Production const banxa = new Banxa('your-partner-name'); // Sandbox const banxa = new Banxa('your-partner-name', 'sandbox'); ``` Replace `your-partner-name` with the partner subdomain provided by Banxa (e.g., `binance`, `metamask`). --- ## Redirect to a new tab Open the Banxa checkout in a new browser tab when a button is clicked. ```javascript banxa.redirect('#redirect', { fiatType: 'AUD', coinType: 'BTC', fiatAmount: 500, walletAddress: '3Hiy7HuFcqwkgERyfRSwEHqrwSwTirm8zb', theme: 'dark' }); ``` ```html ``` The first argument (`#redirect`) is a CSS selector for the element that triggers the checkout on click. --- ## Embed as an iFrame Inject an iFrame into a container element in your page. ```javascript banxa.iframe( '#iframeButton', // trigger element selector '#iframeTarget', // container element selector { fiatType: 'AUD', coinType: 'BTC', fiatAmount: 200, walletAddress: '3Hiy7HuFcqwkgERyfRSwEHqrwSwTirm8zb' }, '800px', // optional width (pass false to omit) '400px' // optional height (pass false to omit) ); ``` ```html
``` --- ## Supported parameters All [referral URL parameters](./supported-parameters.md) are supported as keys in the options object passed to `redirect()` and `iframe()`. --- ## Notes - The SDK generates a referral URL from the options you provide and opens it via redirect or iFrame — it does not make server-to-server API calls. - For server-to-server order creation (with webhooks, order IDs, and KYC sharing), use the [API integration](../api-integration/api-integration-overview.md) instead. --- Source: https://docs.banxa.com/products/hosted-checkout/docs/sdk-integration/overview.md --- title: "SDK Integration Overview | Banxa Docs" description: "Integrate Banxa Hosted Checkout on mobile with the React Native SDK. Type-safe order creation and checkout presentation for mobile apps." --- # SDK Integration Overview The Banxa React Native SDK wraps Banxa Hosted Checkout in a TypeScript-first mobile SDK. Install a single package, configure it with your API key, and call typed methods to fetch quotes, create orders, and present the checkout — all inside your mobile app. This is the fastest way to integrate Banxa Hosted Checkout into a React Native mobile app. Authentication uses the same `x-api-key` header as the API; no backend is required for core flows. --- ## When to use the SDK Choose the SDK if: - You're building a React Native mobile app. - You want faster time to integration than wiring up API calls manually. - You want a type-safe interface with typed request and response models. - You want the SDK to handle WebView presentation, navigation events, and return URL detection. For some capabilities, you'll need the API integration path directly — either alongside the SDK, or as your primary integration path: | Capability | Path | |---|---| | Web apps | [Referral integration](../referral-integration/overview.md) (JS SDK) or [API integration](../api-integration/api-integration-overview.md) | | KYC data sharing (Sumsub token share) | [API integration](../api-integration/api-integration-overview.md) — uses HMAC auth, which the SDK does not support | | Full order list across all customers | `GET /v2/orders` via [API integration](../api-integration/api-integration-overview.md) — the SDK supports lookup by ID or customer only | --- ## How it compares | | Referral (JS SDK) | API | React Native SDK | |---|---|---|---| | Integration effort | Lowest | Highest | Low | | Platform | Web | Any | React Native mobile | | Backend required | No | No (only for KYC sharing) | No | | Webhooks | No | Yes | Yes (configured separately) | | Order lookup | No | Yes (all orders) | Yes (by ID or by customer) | | KYC sharing | No | Yes | No | | Quote UI in your app | No | Yes | Yes | | Checkout presentation | Redirect or iFrame | You choose | In-app WebView | --- ## Next steps - [SDK Integration Guide](./integration-guide.md) — end-to-end walkthrough with code. - [SDK Reference](./sdk-reference.md) — full method reference. --- Source: https://docs.banxa.com/products/hosted-checkout/docs/sdk-integration/integration-guide.md --- title: "SDK Integration Guide — React Native | Banxa Docs" description: "End-to-end React Native SDK integration for Banxa Hosted Checkout: install, configure, quote, create order, present checkout, handle completion." --- # SDK Integration Guide This guide walks through a complete Banxa Hosted Checkout integration using the React Native SDK. By the end, you'll have a working buy flow: fetch a live quote, create an order, present the checkout in a WebView, and retrieve the final order status. --- ## Before you start ### Prerequisites - A React Native mobile app (iOS, Android, or both). - Your Banxa partner reference and API key (sandbox for development, production after approval). - A configured webhook endpoint (optional but recommended for order status tracking). ### Install the SDK ```bash npm install @banxa-official/react-native-sdk react-native-webview ``` For iOS, install the pods: ```bash cd ios && pod install ``` `react-native-webview` is a peer dependency. --- ## Step 1 — Initialise the SDK Create a single `Banxa` client instance at app startup. Keep it in a shared service or context so it can be reused across screens. ```typescript import { Banxa } from '@banxa-official/react-native-sdk'; const banxa = new Banxa({ apiKey: 'YOUR_API_KEY', partner: 'your-partner-id', environment: 'sandbox', // or 'production' }); ``` | Field | Description | |---|---| | `apiKey` | Your Banxa API key. | | `partner` | Your partner identifier (e.g., `binance`, `metamask`). | | `environment` | `'sandbox'` or `'production'`. Defaults to `'production'`. | | `baseUrl` | Optional. Overrides the environment-derived base URL. | --- ## Step 2 — Get a quote Call the Quote API to retrieve live pricing before presenting an amount to the customer. Call this close to when you show the price — crypto rates move quickly, and stale quotes can differ from the final checkout price. ```typescript const quote = await banxa.prices.getBuyQuote({ fiat: 'USD', crypto: 'BTC', fiatAmount: '100', paymentMethodId: '1', blockchain: 'bitcoin', }); console.log('Crypto amount:', quote.cryptoAmount); console.log('Processing fee:', quote.processingFee); console.log('Network fee:', quote.networkFee); ``` Either `fiatAmount` or `cryptoAmount` must be provided. For sell quotes, use `banxa.prices.getQuote('sell', { ... })`. --- ## Step 3 — Create an order Create a buy order once the customer confirms. ```typescript const order = await banxa.buy.createOrder({ externalCustomerId: 'user-123', fiat: 'USD', crypto: 'BTC', fiatAmount: '100', paymentMethodId: '1', walletAddress: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', redirectUrl: 'https://yourapp.com/success', }); ``` ### Required fields - `externalCustomerId` — your stable customer identifier, used by Banxa to recognise returning customers. - `fiat`, `crypto`, and either `fiatAmount` or `cryptoAmount`. - `paymentMethodId` — pre-selects the payment method. - `walletAddress` — the customer's receiving wallet address. - `redirectUrl` — where the customer is returned after checkout. ### Response ```json { "checkoutUrl": "https://partner.banxa.com/portal?expires=xxx&oid=xxx&signature=xxx", "id": "191fa5b4b1f45e1cf784422e09317d56", "externalOrderId": "a4b427ccb872a1744b317456bd0d165f", "externalCustomerId": "user-123", "fiat": "CAD", "fiatAmount": "1000.00", "crypto": "USDC", "cryptoAmount": null, "blockchain": "TRON" } ``` | Field | Description | |---|---| | `checkoutUrl` | Banxa-hosted checkout URL to present to the customer. | | `id` | Banxa order ID. Store this — you'll use it for order lookup. | | `externalOrderId` | Your internal order reference, if provided at creation. `null` if not. | | `externalCustomerId` | Your customer identifier as supplied. | | `fiat`, `fiatAmount`, `crypto`, `cryptoAmount`, `blockchain` | Order parameters as supplied. If `cryptoAmount` or `fiatAmount` wasn't provided, it will be `null`. | --- ## Step 4 — Present the checkout Use `initializeCheckoutWebView` to configure the props for the `CheckoutWebView` component, then render it. ```typescript import { CheckoutWebView } from '@banxa-official/react-native-sdk'; import { useState } from 'react'; function BuyCryptoScreen() { const [webViewProps, setWebViewProps] = useState(null); const handleBuyPress = async () => { const order = await banxa.buy.createOrder({ externalCustomerId: 'user-123', fiat: 'USD', crypto: 'BTC', fiatAmount: '100', paymentMethodId: '1', walletAddress: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', redirectUrl: 'https://yourapp.com/success', }); const props = banxa.buy.initializeCheckoutWebView(order, { onClose: () => setWebViewProps(null), onSuccess: (url) => { /* payment succeeded */ setWebViewProps(null); }, onFailure: (url) => { /* payment failed */ setWebViewProps(null); }, returnUrlOnSuccess: 'https://yourapp.com/success', returnUrlOnFailure: 'https://yourapp.com/failure', }); setWebViewProps(props); }; return ( <>