# Global Support Source: https://docs.zbdpay.com/earn/coverage List of regions where ZBD App & Earn SDK is supported. **This page lists region coverage for ZBD Earn products (API, SDK and App)**. For ZBD Payments product region coverage, please visit [Payments Coverage](/payments/coverage). ## US States The ZBD App & Earn SDK are available in all US states, **except New York (NY)**. ## Countries The ZBD App & Earn SDK **ARE** available in the following countries: * Argentina * Australia * Austria * Belgium * Brazil * Bulgaria * Canada * Chile * Croatia * Cyprus * Czech Republic * Denmark * Estonia * Finland * France * Germany * Greece * Hungary * Iceland * Ireland * Israel * Italy * Japan * Korea, Republic of * Latvia * Lithuania * Luxembourg * Malta * Netherlands * New Zealand * Norway * Poland * Portugal * Qatar * Romania * Singapore * Slovakia * Slovenia * South Africa * Spain * Sweden * Switzerland * Taiwan * Turkey * United Arab Emirates * United Kingdom * United States ## Cashout Options Users in supported regions can withdraw their earned rewards through the following cashout methods. All cashout options are only available in the [supported regions listed above](#countries), excluding countries listed on the [Payments unsupported regions](/payments/coverage) page due to sanctions or high regulatory risk. } href="/earn/app"> Available in all supported Earn regions **except** the following countries where the ZBD App is not available on Google Play (Android only): Canada, Israel, South Africa, Switzerland, and the United Arab Emirates. } href="https://www.speed.app/"> Available in **all** supported Earn regions. } href="https://cash.app/"> Available in the **United States** only. Available in **all** supported Earn regions. # Knowledge Base Source: https://docs.zbdpay.com/earn/knowledge-base Learn best practices for implementing secure, scalable Bitcoin rewards in your games and apps Your complete resource for mastering Bitcoin rewards integration. From security fundamentals to advanced implementation strategies, we've compiled everything you need to build successful rewarded experiences. **Built from Experience** - These guides come from real-world implementations across hundreds of games processing millions of rewards. Learn from what works. ## What You'll Find Here Protect your rewards from cheaters and hackers Proven patterns from successful integrations Step-by-step integration for Unity, Unreal, and more ## Featured Courses ### ๐ŸŽฎ Rewarded Play Guide Learn how to implement Bitcoin rewards that drive engagement without breaking your game economy. **Essential for Game Developers** - Understand reward economics, player psychology, and implementation strategies that have driven 40% retention increases. **What's Covered:** * Designing sustainable reward economies * Balancing fun gameplay with real rewards * Anti-cheat strategies specific to rewarded games * Case studies from successful implementations ### ๐Ÿ”’ Game Security Course **Required Reading** - When real money is involved, security isn't optional. This comprehensive course covers every attack vector and how to defend against it. Our 9-part security course takes you through: Start with a vulnerable example game Learn how hackers exploit games: * Hacking saved game files * Memory manipulation * Source code decompilation * Network traffic interception Implement professional security: * Server-side validation * Anti-tampering measures * Replay attack prevention * Device verification **9 Comprehensive Modules** - From basic vulnerabilities to advanced protection strategies. With code examples and real attack scenarios. ### ๐Ÿ› ๏ธ Integration Guides Platform-specific guides for seamless implementation: **Most Popular** - Complete Unity integration with our drag-and-drop SDK * C# code examples * Prefab components * iOS/Android build guides * Performance optimization **Backend Integration** - Server-side rewards with Beamable * Microservice setup * Player economy design * Secure API patterns * Scaling strategies ## Why This Knowledge Matters ### The Cost of Getting It Wrong **Without Proper Security:** * Hackers drain reward pools * Fake rewards flood the system * Player trust destroyed * Potential legal issues **One game lost \$50,000 in 24 hours** due to a simple client-trust vulnerability. **Common Mistakes Cost:** * Players exploit imbalanced rewards * Server costs spiral out of control * Retention actually decreases * Negative reviews pile up **Proper implementation drives 3-5x ROI** on reward spend. **Growing Pains:** * Rewards system can't handle success * Database locks under load * API rate limits hit * Players face delays **Plan for 100x growth** from day one. ## Learning Paths Choose your journey based on your role: ### For Game Developers Start with security fundamentals (2-3 hours) Learn reward design principles (1 hour) Implement in your engine (2-4 hours) Monitor, adjust, optimize ### For Backend Engineers Focus on modules 5-9 for server security Study rate limiting and validation If using microservices architecture ### For Product Managers Understand the economics Know the risks and mitigations Learn from successful implementations ## Quick Reference ### Security Checklist * [ ] Never trust the client * [ ] Validate all rewards server-side * [ ] Implement rate limiting * [ ] Use cryptographic signatures * [ ] Monitor for anomalies * [ ] Regular security audits * [ ] Incident response plan ### Common Vulnerabilities | Attack Type | Risk Level | Prevention | | -------------------- | ---------- | ------------------------- | | Memory hacking | ๐Ÿ”ด High | Server validation | | Replay attacks | ๐Ÿ”ด High | Nonce/timestamp checks | | Save file tampering | ๐ŸŸก Medium | Encryption + server sync | | Network interception | ๐ŸŸก Medium | TLS + certificate pinning | | Fake devices | ๐ŸŸก Medium | Device attestation | ## Community Wisdom **THNDR Games**: "The security course saved us from a major vulnerability before launch" **Bitcoin Miner**: "Rewarded play guide helped us balance rewards perfectly" **Common Insight**: "We wish we'd read the security course before writing any code" **Best Practice**: "Server-side validation isn't optional - it's essential" ## Stay Updated The landscape of rewarded gaming evolves rapidly: * **New Attack Vectors** - We update the security course as new threats emerge * **Platform Changes** - Integration guides updated for latest SDKs * **Best Practices** - Community-driven improvements **Continuous Learning** - Bookmark this knowledge base and check back regularly. We add new content based on developer feedback and emerging trends. ## Get Started Don't wait until after launch to think about security and best practices: Essential for every developer Design sustainable economies Most popular platform guide *** **Talk to Our Team** - Get personalized advice for your specific implementation challenges. # Introduction Source: https://docs.zbdpay.com/earn/knowledge-base/game-security ZBD's Head of R&D and Game Security Christian Moss introduces us to the series. ``` ## Framework Integrations This is the core package. For framework-specific integrations, see: React components and hooks React Native components Flutter plugin ## Try It Out ### Interactive Example To try the interactive example locally: 1. Clone the repository: ```bash theme={null} git clone https://github.com/zbdpay/ramp-ts.git cd ramp-ts ``` 2. Start a local server: ```bash theme={null} npx http-server . -p 8000 -o /example/js.html ``` 3. Fill the form with your API key and user details 4. Click "Create Session & Load Ramp" to see it in action ### CodeSandbox Example Try it online: [CodeSandbox Demo](https://codesandbox.io/s/zbd-ramp-example) ## Resources * [GitHub Repository](https://github.com/zbdpay/ramp-ts) * [NPM Package](https://www.npmjs.com/package/@zbdpay/ramp-ts) * [API Documentation](/payments/ramp/session) * [Webhook Events](/payments/ramp/webhooks) ## Support For support and questions: * GitHub Issues: [Create an issue](https://github.com/zbdpay/ramp-ts/issues) # Create Ramp Session Source: https://docs.zbdpay.com/payments/ramp/session POST https://api.zbdpay.com/api/v1/ramp-widget Initialize a new ramp widget session for a user ## API Overview ### Base URL All API requests should be made to: ``` https://api.zbdpay.com ``` ### Authentication All ZBD endpoints are protected by an API Key. To make requests against these endpoints you must pass a header property called `apikey` with your [ZBD Project API Key](/get-started/api-keys). ```json theme={null} apikey: "your-api-key-here" ``` Get your API keys by [scheduling a call](https://zbd.one/sales) with our sales team. ### SSL / HTTPS Access **ZBD only provides a secure interface over HTTPS with SSL certificate support**. Any requests that attempt to reach the ZBD API in an insecure fashion (plain-text over HTTP requests) will be rejected. ## Description This endpoint creates a session token and widget URL that can be used to embed the ZBD Ramp widget in your game or application. ## Usage The ramp widget allows your users to purchase Bitcoin using their preferred fiat currency. The session creation process involves: Create a session with user details and configuration options Receive a widget URL and session token to embed in your application ## Configuration ### Header Parameters ZBD Project API Key Content Type ### Body Parameters Email address of the user launching the Ramp URL to receive webhook notifications Existing user session token (for returning users) Currency to convert from (e.g., USD) Currency to convert to (e.g., BTC) Destination address for the funds (Lightning Address or onchain address) Your internal reference ID for this transaction Additional metadata for the transaction ```bash cURL theme={null} curl -X POST https://api.zbdpay.com/api/v1/ramp-widget \ -H "Content-Type: application/json" \ -H "apikey: YOUR_API_KEY" \ -d '{ "email": "user@example.com", "webhook_url": "https://yourapp.com/webhooks/zbd" }' ``` ```javascript Node.js theme={null} const response = await fetch('https://api.zbdpay.com/api/v1/ramp-widget', { method: 'POST', headers: { 'Content-Type': 'application/json', 'apikey': process.env.ZBD_API_KEY }, body: JSON.stringify({ email: 'user@example.com', webhook_url: 'https://yourapp.com/webhooks/zbd' }) }); const data = await response.json(); ``` ```json Response theme={null} { "success": true, "data": { "session_token": "eyJraWQiOiJzLWE1OWNkMjc4...", "widget_url": "https://ramp.zbdpay.com/?session=eyJraWQiOiJzLWE1OWNkMjc4...", "session_id": "ses_9n3f7h2u4b", "expires_at": "2025-06-09T12:00:00Z" } } ``` ### Error Responses ```json theme={null} { "success": false, "error": { "code": "INVALID_EMAIL", "message": "Please provide a valid email address" } } ``` Common error codes: * `INVALID_EMAIL` - Email format is invalid * `INVALID_WEBHOOK_URL` - Webhook URL is not accessible * `UNAUTHORIZED` - Invalid API key * `RATE_LIMITED` - Too many requests ## Webhook Events Webhooks are sent as POST requests to your specified `webhook_url`. ### Event Structure All webhook events follow this structure: ```json theme={null} { "id": "evt_2n4f8gu3nf", "type": "ramp.purchase.completed", "created_at": "2025-06-09T10:30:00Z", "livemode": true, "data": { // Event-specific data } } ``` # Themes Customization Source: https://docs.zbdpay.com/payments/ramp/themes Customize every visual element of ZBD Ramp to match your brand ZBD Ramp provides complete control over the widget's appearance through an advanced theming system. Customize every visual element to create a seamless brand experience. ## Theme Options ### Pre-built Themes Start with our professionally designed themes: Clean, bright interface perfect for daytime use and professional applications Modern dark interface that reduces eye strain and saves battery on OLED screens ## Live Preview Editor ZBD Ramp Live Preview Editor The ZBD Developer Dashboard features a powerful Live Preview editor that makes customization intuitive and immediate. ### How It Works Navigate to Ramp โ†’ Theme in your ZBD Developer Dashboard Click directly on any element in the preview to select it Adjust the element's properties in the side panel Watch your changes apply in real-time Save your custom theme when satisfied ### Customizable Properties For every element in the widget, you can customize: * **Background Color**: Primary background color * **Text Color**: Primary text color * **Border Color**: Element border color * **Border Radius**: Corner rounding * **Hover Background**: Background color on hover * **Hover Text**: Text color on hover * **Active Background**: Background when clicked/active * **Active Text**: Text color when active * **Disabled State**: Appearance when disabled * **Shadow**: Box shadow effects * **Padding & Margins**: Spacing adjustments ## Custom Theme JSON Every theme configuration is exportable as JSON, allowing you to: * Version control your theme * Share themes across projects * Backup theme configurations * Programmatically apply themes ### Example Theme JSON ```json theme={null} { "name": "My Custom Theme", "base": "light", "variables": { "--primary": "210 100% 50%", "--primary-foreground": "0 0% 100%", "--secondary": "210 40% 96%", "--secondary-foreground": "222 47% 11%", "--accent": "210 40% 90%", "--accent-foreground": "222 47% 11%", "--destructive": "0 84% 60%", "--destructive-foreground": "0 0% 100%", "--border": "214 32% 91%", "--input": "214 32% 91%", "--ring": "210 100% 50%", "--radius": "0.5rem", "--widget-background": "#FFFFFF", "--widget-card-background": "#FAFAFA", "--amount-input-display-text": "#000000", "--amount-input-display-error": "#ef4444", "--amount-input-quick-amount-bg": "#F5F5F5", "--amount-input-quick-amount-text": "#000000", "--amount-input-quick-amount-border": "#E0E0E0", "--amount-input-quick-amount-hover-bg": "#EEEEEE", "--amount-input-quick-amount-active-bg": "#E0E0E0", "--amount-input-numpad-bg": "transparent", "--amount-input-numpad-text": "#000000", "--amount-input-numpad-hover-bg": "#F5F5F5", "--amount-input-numpad-active-bg": "#EEEEEE", "--button-primary-bg": "210 100% 50%", "--button-primary-text": "0 0% 100%", "--button-primary-hover-bg": "210 100% 45%", "--button-primary-active-bg": "210 100% 40%", "--button-secondary-bg": "210 40% 96%", "--button-secondary-text": "222 47% 11%", "--button-secondary-hover-bg": "210 40% 92%", "--button-secondary-active-bg": "210 40% 88%" } } ``` ### Importing/Exporting Themes #### Export Your Theme 1. Open the Theme editor in your dashboard 2. Click "Export Theme" button 3. Copy the JSON or download as a file #### Import a Theme 1. Open the Theme editor 2. Click "Import Theme" 3. Paste your JSON or upload a file 4. Preview and apply ## Version Control Integration ### Best Practices 1. **Store in Repository**: Keep your theme JSON in your project repository 2. **Use Environment Configs**: Different themes for dev/staging/production 3. **Document Changes**: Comment significant theme updates 4. **Test Across Devices**: Ensure theme works on all screen sizes ## Testing Your Theme Always test your custom theme across different devices and browsers to ensure a consistent experience. ## Support Need help with theme customization? * Use the Live Preview in your [ZBD Developer Dashboard](https://dashboard.zbdpay.com) * Contact your ZBD account manager for advanced customization * Review our example themes in the dashboard # User Flow Walkthrough Source: https://docs.zbdpay.com/payments/ramp/user-flow Visual walkthrough of the complete ZBD Ramp user experience This guide walks through the complete user journey in the ZBD Ramp widget, from initial screen to completed purchase. Use this reference to understand what your users will experience when buying Bitcoin through your app. ## 1. Initial Screen When users first launch the ramp widget, they're greeted with a clean, intuitive interface to begin their purchase journey. ZBD Ramp Initial Screen The initial screen presents the core functionality and guides users to begin the purchase process. *** ## 2. Email Verification To ensure account security and compliance, users must verify their email address before proceeding. Email Verification Screen Users receive a verification code via email and enter it to confirm their identity and proceed to KYC. *** ## 3. KYC Flow The Know Your Customer (KYC) process is streamlined into clear steps. This is required for regulatory compliance and fraud prevention. ### Step 1: Getting Started KYC Initial Screen Users are introduced to the KYC process and what information they'll need to provide. ### Step 2: Country Selection Country Selection Users select their country of residence. This determines available payment methods and compliance requirements. ### Step 3: Personal Information Personal Information Form Users provide basic personal information including full name, date of birth, and other required details. ### Step 4: Address Address Form Users enter their residential address for identity verification purposes. ### Step 5: Document Selection Document Type Selection Users choose which type of government-issued ID they'll upload (passport, driver's license, etc.). ### Step 6: Document Upload Document Upload Screen Users can upload documents directly or receive a secure link to complete the upload on their phone. Upload from Phone Users can scan a QR code or receive a link to upload from their mobile device. Secure Link Option A secure, one-time link is generated for mobile document uploads. Phone Connected Confirmation that the mobile device is connected and ready for upload. *** ## 4. KYC Processing After submitting KYC information, users enter the verification stage. KYC Processing Users see a processing screen while their information is being verified. This typically takes just a few moments. KYC Success Upon successful verification, users are cleared to proceed with their purchase. KYC Failed If verification fails, users receive clear guidance on next steps or alternative options. *** ## 5. Payment Method Selection Once KYC is approved, users choose how they want to pay for their Bitcoin. Payment Method Selection Users can connect their bank account via Plaid for ACH payments. Additional payment methods are coming soon. If a user hasn't linked a bank account yet, they'll see an option to connect via Plaid. Payment Method - No Bank Linked *** ## 6. Plaid Bank Connection To enable bank payments, users securely connect their bank account through Plaid. ### Plaid Flow Steps Plaid Initial Screen Users are introduced to the secure Plaid connection process. Bank Institution Selection Users search for and select their bank from thousands of supported institutions. Bank Authentication Users log into their bank account securely through Plaid's interface. Bank Connected Successfully Confirmation that the bank account is successfully connected and ready for payments. *** ## 7. Payment Confirmation After payment method setup, users confirm their purchase details. Payment Confirmation Success Users see a success screen confirming their Bitcoin purchase and delivery details. Payment Confirmation Failed If the payment fails, users receive clear error messages and options to retry. *** ## 8. Transaction Management Users can view their transaction history and details at any time. ### Transaction History Transaction History A comprehensive list of all past and pending transactions, with status indicators for easy tracking. ### Transaction Details Transaction Details Detailed view of individual transactions including amounts, timestamps, fees, and destination addresses. *** ## Integration Tips Design your app to seamlessly transition users into the ramp widget. Consider the context and timing of when you present the purchase option. Implement webhook handlers to process purchase events and update your app state in real-time as users progress through the flow. *** ## Next Steps Learn how to integrate the ZBD Ramp widget into your application Customize the widget's theme to match your brand Handle webhook events to track user progress and purchases Explore the complete API documentation # Webhook Events Source: https://docs.zbdpay.com/payments/ramp/webhooks Complete reference for ZBD Ramp webhook events ## Overview Webhooks allow you to receive real-time notifications about events that occur during the ZBD Ramp lifecycle. From session initialization to payment completion, webhooks provide crucial updates about your users' transactions. ZBD Ramp sends webhook events to your specified endpoint as HTTP POST requests. Each event contains detailed information about what occurred, allowing you to update your systems accordingly. ### Webhook URL Setup Configure your webhook URL when creating a session: ```typescript @zbdpay/ramp-ts theme={null} import { initRampSession, QuoteCurrencyEnum, BaseCurrencyEnum } from '@zbdpay/ramp-ts'; const response = await initRampSession({ apikey: process.env.ZBD_API_KEY, email: 'user@example.com', destination: 'lightning-address@zbd.gg', quote_currency: QuoteCurrencyEnum.USD, base_currency: BaseCurrencyEnum.BTC, webhook_url: 'https://app.xyz/webhooks/zbd', // <---- YOUR WEBHOOK URL HERE reference_id: 'order-123', metadata: { userId: 'user-456', plan: 'premium' } }); const sessionToken = response.data.session_token; ``` ```typescript Fetch theme={null} const response = await fetch('https://api.zbdpay.com/api/v1/ramp-widget', { method: 'POST', headers: { 'Content-Type': 'application/json', 'apikey': process.env.ZBD_API_KEY }, body: JSON.stringify({ email: 'user@example.com', destination: 'lightning-address@zbd.gg', quote_currency: 'USD', base_currency: 'BTC', webhook_url: 'https://app.xyz/webhooks/zbd', // <---- YOUR WEBHOOK URL HERE reference_id: 'order-123', metadata: { userId: 'user-456', plan: 'premium' } }) }); const data = await response.json(); const sessionToken = data.data.session_token; ``` ## Event Categories Webhook events are organized into six main categories: Widget lifecycle events User verification events Identity verification events Bank connection events Transaction events Crypto delivery events ## Event Structure All webhook events follow a consistent structure: ```json theme={null} { "event": "RAMP_WIDGET.V1.EVENT_NAME", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", // Event-specific data "metadata": { // Your custom metadata } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:30:00Z" } ``` ## Complete Event Reference ### Session Events **Event:** `RAMP_WIDGET.V1.SESSION.INITIATED` Triggered when a Ramp session is successfully initiated. ```json theme={null} { "event": "RAMP_WIDGET.V1.SESSION.INITIATED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789" }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:30:00Z" } ``` **Event:** `RAMP_WIDGET.V1.SESSION.CLOSED` Fired when a Ramp session is closed, either by unmount, user action, or completion. ```json theme={null} { "event": "RAMP_WIDGET.V1.SESSION.CLOSED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789" }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T11:15:00Z" } ``` ### Authentication Events **Event:** `RAMP_WIDGET.V1.CODE_VERIFICATION.SUCCEEDED` Sent when a user successfully verifies their 6-digit code. ```json theme={null} { "event": "RAMP_WIDGET.V1.CODE_VERIFICATION.SUCCEEDED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789" }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:32:00Z" } ``` **Event:** `RAMP_WIDGET.V1.CODE_VERIFICATION.FAILED` Triggered when a 6-digit code verification attempt fails. ```json theme={null} { "event": "RAMP_WIDGET.V1.CODE_VERIFICATION.FAILED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789" }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:33:00Z" } ``` **Event:** `RAMP_WIDGET.V1.USER_ACCESS_TOKEN.CREATED` Fired when user access and refresh tokens are generated. ```json theme={null} { "event": "RAMP_WIDGET.V1.USER_ACCESS_TOKEN.CREATED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "access_token_id": "tok_abc123", "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "access_token_expires_at": "2025-01-18T11:45:00Z", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token_expires_at": "2025-02-18T10:45:00Z", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:45:00Z" } ``` ### KYC Events **Event:** `RAMP_WIDGET.V1.KYC.INITIATED` Fired when KYC (Know Your Customer) verification process is initiated. ```json theme={null} { "event": "RAMP_WIDGET.V1.KYC.INITIATED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:35:00Z" } ``` **Event:** `RAMP_WIDGET.V1.KYC.PROCESSING` Indicates that KYC verification is being processed. ```json theme={null} { "event": "RAMP_WIDGET.V1.KYC.PROCESSING", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:36:00Z" } ``` **Event:** `RAMP_WIDGET.V1.KYC.COMPLETED` Sent when KYC verification is successfully completed. ```json theme={null} { "event": "RAMP_WIDGET.V1.KYC.COMPLETED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:40:00Z" } ``` **Event:** `RAMP_WIDGET.V1.KYC.REJECTED` Triggered when KYC verification is rejected or fails. ```json theme={null} { "event": "RAMP_WIDGET.V1.KYC.REJECTED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:41:00Z" } ``` ### Bank Connection Events **Event:** `RAMP_WIDGET.V1.BANK_CONNECTION.INITIATED` Triggered when bank connection flow is initiated. ```json theme={null} { "event": "RAMP_WIDGET.V1.BANK_CONNECTION.INITIATED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:50:00Z" } ``` **Event:** `RAMP_WIDGET.V1.BANK_CONNECTION.COMPLETED` Sent when bank connection is successfully established. ```json theme={null} { "event": "RAMP_WIDGET.V1.BANK_CONNECTION.COMPLETED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:55:00Z" } ``` **Event:** `RAMP_WIDGET.V1.BANK_CONNECTION.FAILED` Fired when bank connection attempt fails. ```json theme={null} { "event": "RAMP_WIDGET.V1.BANK_CONNECTION.FAILED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T10:56:00Z" } ``` ### Payment Events **Event:** `RAMP_WIDGET.V1.PAYMENT.INITIATED` Triggered when a payment transaction is initiated. ```json theme={null} { "event": "RAMP_WIDGET.V1.PAYMENT.INITIATED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "base_amount": "100.00", "base_currency": "USD", "quote_amount": "0.0025", "quote_currency": "BTC", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T11:00:00Z" } ``` **Event:** `RAMP_WIDGET.V1.PAYMENT.SETTLED` Sent when a payment transaction is successfully settled. ```json theme={null} { "event": "RAMP_WIDGET.V1.PAYMENT.SETTLED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "base_amount": "100.00", "base_currency": "USD", "quote_amount": "0.0025", "quote_currency": "BTC", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T11:05:00Z" } ``` **Event:** `RAMP_WIDGET.V1.PAYMENT.FAILED` Fired when a payment transaction fails. ```json theme={null} { "event": "RAMP_WIDGET.V1.PAYMENT.FAILED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "base_amount": "100.00", "base_currency": "USD", "quote_amount": "0.0025", "quote_currency": "BTC", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T11:06:00Z" } ``` ### Withdrawal Events **Event:** `RAMP_WIDGET.V1.WITHDRAWAL.SUCCEEDED` Triggered when a withdrawal is successfully completed. ```json theme={null} { "event": "RAMP_WIDGET.V1.WITHDRAWAL.SUCCEEDED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "base_amount": "100.00", "base_currency": "USD", "quote_amount": "0.0025", "quote_currency": "BTC", "destination": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "payment_hash": "c0ff35a42445041592aa5ff982606971ae46b3f9df0a100cb15f05f61718f223", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T11:10:00Z" } ``` **Event:** `RAMP_WIDGET.V1.WITHDRAWAL.FAILED` Sent when a withdrawal attempt fails. ```json theme={null} { "event": "RAMP_WIDGET.V1.WITHDRAWAL.FAILED", "data": { "email": "user@example.com", "session_id": "sess_abc123xyz789", "base_amount": "100.00", "base_currency": "USD", "quote_amount": "0.0025", "quote_currency": "BTC", "destination": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "metadata": { "customer_id": "cust_123", "source": "widget" } }, "url": "https://partner.com/webhooks", "created_at": "2025-01-18T11:11:00Z" } ``` ## Webhook Implementation ### Basic Handler Examples Here are webhook handler implementations in popular frameworks: ```typescript Next.js theme={null} // app/api/webhooks/zbd/route.ts import { NextRequest, NextResponse } from 'next/server'; export async function POST(request: NextRequest) { const event = await request.json(); // Log the event console.log(`Received webhook: ${event.event}`); // Handle different event types switch(event.event) { case 'RAMP_WIDGET.V1.PAYMENT.SETTLED': // Update user balance, send confirmation, etc. await handlePaymentSettled(event.data); break; case 'RAMP_WIDGET.V1.KYC.COMPLETED': // Update user verification status await handleKYCCompleted(event.data); break; case 'RAMP_WIDGET.V1.WITHDRAWAL.SUCCEEDED': // Log successful withdrawal await handleWithdrawalSuccess(event.data); break; default: console.log(`Unhandled event type: ${event.event}`); } // Always respond with 200 OK return NextResponse.json({ received: true }, { status: 200 }); } async function handlePaymentSettled(data: any) { // Your payment processing logic console.log('Payment settled:', data); } async function handleKYCCompleted(data: any) { // Your KYC completion logic console.log('KYC completed:', data); } async function handleWithdrawalSuccess(data: any) { // Your withdrawal success logic console.log('Withdrawal succeeded:', data); } ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" ) type WebhookEvent struct { Event string `json:"event"` Data map[string]interface{} `json:"data"` URL string `json:"url"` CreatedAt string `json:"created_at"` } func webhookHandler(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } body, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, "Error reading body", http.StatusBadRequest) return } var event WebhookEvent err = json.Unmarshal(body, &event) if err != nil { http.Error(w, "Error parsing JSON", http.StatusBadRequest) return } // Log the event log.Printf("Received webhook: %s", event.Event) // Handle different event types switch event.Event { case "RAMP_WIDGET.V1.PAYMENT.SETTLED": handlePaymentSettled(event.Data) case "RAMP_WIDGET.V1.KYC.COMPLETED": handleKYCCompleted(event.Data) case "RAMP_WIDGET.V1.WITHDRAWAL.SUCCEEDED": handleWithdrawalSuccess(event.Data) default: log.Printf("Unhandled event type: %s", event.Event) } // Always respond with 200 OK w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "OK") } func handlePaymentSettled(data map[string]interface{}) { // Your payment processing logic log.Printf("Payment settled: %v", data) } func handleKYCCompleted(data map[string]interface{}) { // Your KYC completion logic log.Printf("KYC completed: %v", data) } func handleWithdrawalSuccess(data map[string]interface{}) { // Your withdrawal success logic log.Printf("Withdrawal succeeded: %v", data) } func main() { http.HandleFunc("/webhooks/zbd", webhookHandler) log.Println("Server starting on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } ``` ```rust Rust theme={null} use actix_web::{web, App, HttpResponse, HttpServer, Result}; use serde::{Deserialize, Serialize}; use serde_json::Value; #[derive(Debug, Deserialize, Serialize)] struct WebhookEvent { event: String, data: Value, url: String, created_at: String, } async fn webhook_handler(event: web::Json) -> Result { // Log the event println!("Received webhook: {}", event.event); // Handle different event types match event.event.as_str() { "RAMP_WIDGET.V1.PAYMENT.SETTLED" => { handle_payment_settled(&event.data).await; } "RAMP_WIDGET.V1.KYC.COMPLETED" => { handle_kyc_completed(&event.data).await; } "RAMP_WIDGET.V1.WITHDRAWAL.SUCCEEDED" => { handle_withdrawal_success(&event.data).await; } _ => { println!("Unhandled event type: {}", event.event); } } // Always respond with 200 OK Ok(HttpResponse::Ok().body("OK")) } async fn handle_payment_settled(data: &Value) { // Your payment processing logic println!("Payment settled: {:?}", data); } async fn handle_kyc_completed(data: &Value) { // Your KYC completion logic println!("KYC completed: {:?}", data); } async fn handle_withdrawal_success(data: &Value) { // Your withdrawal success logic println!("Withdrawal succeeded: {:?}", data); } #[actix_web::main] async fn main() -> std::io::Result<()> { println!("Server starting on http://127.0.0.1:8080"); HttpServer::new(|| { App::new() .route("/webhooks/zbd", web::post().to(webhook_handler)) }) .bind("127.0.0.1:8080")? .run() .await } ``` ```javascript Node.js/Express theme={null} app.post('/webhooks/zbd', (req, res) => { const event = req.body; // Log the event console.log(`Received webhook: ${event.event}`); // Handle different event types switch(event.event) { case 'RAMP_WIDGET.V1.PAYMENT.SETTLED': // Update user balance, send confirmation, etc. handlePaymentSettled(event.data); break; case 'RAMP_WIDGET.V1.KYC.COMPLETED': // Update user verification status handleKYCCompleted(event.data); break; case 'RAMP_WIDGET.V1.WITHDRAWAL.SUCCEEDED': // Log successful withdrawal handleWithdrawalSuccess(event.data); break; default: console.log(`Unhandled event type: ${event.event}`); } // Always respond with 200 OK res.status(200).send('OK'); }); function handlePaymentSettled(data) { // Your payment processing logic console.log('Payment settled:', data); } function handleKYCCompleted(data) { // Your KYC completion logic console.log('KYC completed:', data); } function handleWithdrawalSuccess(data) { // Your withdrawal success logic console.log('Withdrawal succeeded:', data); } ``` ```python Python/Flask theme={null} from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/webhooks/zbd', methods=['POST']) def handle_webhook(): event = request.json # Log the event print(f"Received webhook: {event['event']}") # Handle different event types if event['event'] == 'RAMP_WIDGET.V1.PAYMENT.SETTLED': handle_payment_settled(event['data']) elif event['event'] == 'RAMP_WIDGET.V1.KYC.COMPLETED': handle_kyc_completed(event['data']) elif event['event'] == 'RAMP_WIDGET.V1.WITHDRAWAL.SUCCEEDED': handle_withdrawal_success(event['data']) else: print(f"Unhandled event type: {event['event']}") # Always respond with 200 OK return 'OK', 200 def handle_payment_settled(data): # Your payment processing logic print(f"Payment settled: {data}") def handle_kyc_completed(data): # Your KYC completion logic print(f"KYC completed: {data}") def handle_withdrawal_success(data): # Your withdrawal success logic print(f"Withdrawal succeeded: {data}") if __name__ == '__main__': app.run(port=8080) ``` ## Security Best Practices **Important:** Always validate webhook authenticity before processing events. ### Webhook Verification 1. **Use HTTPS Only** - Always use HTTPS endpoints for webhooks 2. **Verify Signatures** - Validate webhook signatures when provided 3. **IP Allowlisting** - Restrict webhook access to ZBD IP addresses 4. **Idempotency** - Handle duplicate events gracefully 5. **Timeout Handling** - Respond quickly (within 5 seconds) ### Error Handling * Always respond with `200 OK` status, even if processing fails * Log all webhook events for debugging * Implement retry logic for critical operations * Use message queues for asynchronous processing ## Testing Webhooks ### Local Development Use tools like [ngrok](https://ngrok.com) to expose your local webhook endpoint: ```bash theme={null} ngrok http 3000 ``` Then use the ngrok URL as your webhook endpoint during development. ### Webhook Testing Checklist Essential events to handle: * โœ… PAYMENT.SETTLED * โœ… WITHDRAWAL.SUCCEEDED * โœ… PAYMENT.FAILED * โœ… WITHDRAWAL.FAILED Improve user experience: * โœ… KYC.COMPLETED * โœ… KYC.REJECTED * โœ… BANK\_CONNECTION.COMPLETED * โœ… SESSION.CLOSED For advanced integrations: * โšช SESSION.INITIATED * โšช CODE\_VERIFICATION.SUCCEEDED * โšช KYC.INITIATED * โšช PAYMENT.INITIATED ## Troubleshooting ### Common Issues * Verify your webhook URL is publicly accessible * Check for firewall or security rules blocking requests * Ensure your endpoint responds with 200 status * Confirm webhook URL was correctly set during session creation * Implement idempotency using the session\_id * Store processed event IDs to prevent reprocessing * Use database transactions for critical operations * Process webhooks asynchronously using queues * Respond immediately with 200 OK * Move heavy processing to background jobs ## Next Steps Learn how to create widget sessions Complete integration walkthrough # ZBD Payments SDK Source: https://docs.zbdpay.com/payments/sdk Use ZBD Payments in your preferred development environment. ## Official SDKs The following SDKs are maintained by the ZBD Payments core team. Easiest way to interact with the ZBD Payments APIs. Quickstart guides and source code for Next.js, Node.js, and Express applications.
## Community SDKs Supported by the ZBD open source developer community. Rust SDK for ZBD Payments APIs Go SDK for ZBD Payments APIs C# SDK for ZBD Payments APIs # C# SDK Source: https://docs.zbdpay.com/payments/sdk/csharp Learn how to send and receive instant Bitcoin payments with C# and ZBD. ## Prerequisites To complete this guide, you will need the following: * [ZBD Project with a Live API key](/get-started/api-keys) * [Visual Studio](https://visualstudio.microsoft.com/downloads/) ## 1. Create project Create a new project or open an existing project in Visual Studio. ## 2. Install the ZBD NuGet package NuGet package name: `dev.zbd.csharp` ```bash Package Manager theme={null} NuGet\Install-Package dev.zbd.csharp ``` ```bash .NET CLI theme={null} dotnet add package dev.zbd.csharp ``` ```xml Package Reference theme={null} ``` ## 3. Send and receive Bitcoin The below code is an example of how you can create a charge. ```csharp theme={null} using ZebedeeAPI; var handler = new ZebedeeHandler(""); var charge = await handler.CreateCharge( expiresIn: "10000", amount: "100000", description: "A Charge Created with ZBD!" ); Console.WriteLine(charge.data.invoice.request); ``` You're looking for the `data.invoice.request` property in the JSON response. It starts with `lnbc1` and is the payment request anyone in the Bitcoin Lightning Network can use to pay you. ``` lnbc1u1pjdlax9pp5t7jhkd7h2wntd4f2v7xp22dknmjxp0q8nm7hfcny4p7a5mr7x3rsdp9f4hkueteypshggrfde6x2unwv46zqumsv4jkgcqzzsxqzjcsp5dsayu6m6632p28rnkeeqsr7d54amrkv6wh46yrv42gdgca8xl8gs9qyyssqgj2zrkax733rzulfkzc5mqsr8fpwrva82stpa7e0frw32722trv37jlq8mvlqfp8y75lr6mz63zd7qnxar8hhsehuy22pvfq6wjxwqqqa60lx3 ``` Charges and payment requests are usually shown to users as QR codes that can be scanned by mobile apps (e.g. [ZBD](https://zbd.one/download)). Read [Callbacks](/payments/api/callbacks) to understand how to receive updates about your payment asynchronously. # Go SDK Source: https://docs.zbdpay.com/payments/sdk/go Learn how to use the Go SDK to send Bitcoin to any Email. This guide is coming soon. Until then you can check the [Go SDK repository](https://github.com/zebedeeio/go-sdk). # Rust SDK Source: https://docs.zbdpay.com/payments/sdk/rust Learn how to send and receive instant Bitcoin payments with Rust and ZBD. ## Prerequisites To complete this guide, you will need the following: * [ZBD Project with a Live API key](/get-started/api-keys) * [Rust](https://www.rust-lang.org/tools/install) and [cargo](https://www.rust-lang.org/tools/install) installed ## 1. Create a Rust project ```bash theme={null} cargo new cd ``` ## 2. Install dependencies Add both the `zebedee-rust` and `tokio` crates to your project: ```bash theme={null} cargo add zebedee-rust tokio ``` ## 3. Update your Cargo.toml Update your `Cargo.toml` file to use full features in tokio. ```yaml theme={null} [package] name = "zbd-rust" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] tokio = { version = "1.29.1", features = ['full']} zebedee-rust = "0.4.4" ``` ## 4. Create a Charge Open your project's `main.rs` file and add the following code: ```rust theme={null} use std::env; use zebedee_rust::{charges::*, ZebedeeClient}; #[tokio::main] async fn main(){ let apikey: String = env::var("ZBD_API_KEY").unwrap(); let zbd_client = ZebedeeClient::new().apikey(apikey).build(); let charge = Charge{ amount: String::from("5000"), ..Default::default() }; let charges_res = zbd_client.create_charge(&charge).await.unwrap(); println!("{:?}", charge_res); } ``` Make sure to create an environment variable called **ZBD\_API\_KEY** in your Rust project. This is the API key you get from your ZBD Project. Run the following command to create your charge: ```bash theme={null} cargo run ``` You can now create charges using the ZBD and Rust! Charges and payment requests are usually shown to users as QR codes that can be scanned by mobile apps (e.g. [ZBD](https://zbd.one/download)). Read [Callbacks](/payments/api/callbacks) to understand how to receive updates about your payment asynchronously. ## 5. Try it yourself You can now begin receiving instant Bitcoin payments with Rust + ZBD! See the full source code. # TypeScript SDK Source: https://docs.zbdpay.com/payments/sdk/typescript ZBD is your one-stop shop API for all things instant payments in TypeScript. ## Installation ```bash theme={null} pnpm install @zbdpay/payments-sdk ``` ## Quickstart Use the following guides to get started with the TypeScript SDK for ZBD Payments: } href="/payments/sdk/typescript/next"> Application codebase that allows you to quickly get started with ZBD and Next.js -- ZBD API Playground and a Starter Kit for ZBD-powered Next.js apps. } href="/payments/sdk/typescript/vercel/send" /> } href="/payments/sdk/typescript/supabase/send" /> # Express Source: https://docs.zbdpay.com/payments/sdk/typescript/express Learn how to send and receive instant Bitcoin payments with Express and ZBD. ## Prerequisites To complete this guide, you will need the following: * [ZBD Project with a Live API key](/get-started/api-keys) ## 1. Create a Node.js project Start with a brand new empty directory called `express-example` and run the following command to initiate a Node.js project: ```bash theme={null} npm init -y ``` ## 2. Install Express.js Inside your project directory, run the following command to install Express.js: ```bash yarn theme={null} yarn add express ``` ```bash npm theme={null} npm install express ``` ```bash pnpm theme={null} pnpm add express ``` ## 3. Install @zbdpay/payments-sdk Node.js SDK Inside your Express.js ```bash yarn theme={null} yarn add @zbdpay/payments-sdk ``` ```bash npm theme={null} npm install @zbdpay/payments-sdk ``` ```bash pnpm theme={null} pnpm add @zbdpay/payments-sdk ``` ## 4. Send and receive Bitcoin Create a new file called `index.js` and add the following code: ```js theme={null} import express from "express"; import ZbdPayments from '@zbdpay/payments-sdk'; const ZBD_API_KEY = 'b7YW3s2JzZKGcXjIf5Dqof8wjKT2RuWr8'; // dummy key const client = new ZbdPayments({ apikey: ZBD_API_KEY, }); // Create Express app const app = express(); // Creating a Bitcoin Lightning payment request app.get("/request", async (req, res) => { try { const data = await client.lightningCharges.create({ amount: '100000', callbackUrl: 'https://your-app.com/zbd-callback', description: 'Express + ZBD!', }); res.status(200).json({ data }); } catch (error) { res.status(500).json({ error }); } }); // Send a payment to a Bitcoin Lightning Address app.get("/send", async (req, res) => { try { const data = await client.lightningAddress.sendPayment({ lnAddress: "andreneves@zbd.gg", // Who is the recipient? amount: "100000", // 100 satoshis (100,000 msats) comment: "Express + ZBD!", }); res.status(200).json({ data }); } catch (error) { res.status(500).json({ error }); } }); app.listen(3000, () => { console.log("Express server w/ ZBD listening on https://example.com"); }); ``` Run the following command to start your Express.js server: ```bash yarn theme={null} yarn start ``` ```bash npm theme={null} npm start ``` Opening your browser to the following URL: `https://example.com/request` should return a JSON response with a payment request. You can also test this using curl command: ```bash theme={null} curl https://example.com/request ``` You're looking for the `data.invoice.request` property in the JSON response. It starts with `lnbc1` and is the payment request anyone in the Bitcoin Lightning Network can use to pay you. ``` lnbc1u1pjdlax9pp5t7jhkd7h2wntd4f2v7xp22dknmjxp0q8nm7hfcny4p7a5mr7x3rsdp9f4hkueteypshggrfde6x2unwv46zqumsv4jkgcqzzsxqzjcsp5dsayu6m6632p28rnkeeqsr7d54amrkv6wh46yrv42gdgca8xl8gs9qyyssqgj2zrkax733rzulfkzc5mqsr8fpwrva82stpa7e0frw32722trv37jlq8mvlqfp8y75lr6mz63zd7qnxar8hhsehuy22pvfq6wjxwqqqa60lx3 ``` Charges and payment requests are usually shown to users as QR codes that can be scanned by mobile apps (e.g. [ZBD](https://zbd.one/download)). Read [Callbacks](/payments/api/callbacks) to understand how to receive updates about your payment asynchronously. Opening your browser to the following URL: `https://example.com/send` should return a JSON response with the payment sent message. You can also test this using curl command: ```bash theme={null} curl https://example.com/send ``` You're looking for the `status` of `completed` to know that the payment settled successfully. Payments in the Lightning Network are asynchronous so you may see a response stating the payment is `processing`. This is expected -- use the `callbackUrl` property to receive updates about your payments. ## 3. Try it yourself You can now send and receive instant Bitcoin payments using Express.js + ZBD! See the full source code. # Next.js Source: https://docs.zbdpay.com/payments/sdk/typescript/next Learn how to use the ZBD with Next.js and our Playground Starter Kit. ## Playground Starter Kit ZBD + Next.js Playground The ZBD + Next.js Playground Starter Kit is an application codebase that allows you to quickly get started with ZBD and Next.js. It includes two main sections: a ZBD API Playground and a Starter Kit for ZBD-powered Next.js applications. Open the ZBD + Next.js Playground View the source code. } href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fzebedeeio%2Fnextjs-zebedee-starter&env=ZBD_API_KEY&envDescription=Your%20ZBD%20project%20API%20key&demo-title=ZBD%20%2B%20Next.js%20Starter&demo-description=Starter%20kit%20for%20using%20ZBD%20API%20with%20Next.js%20fullstack%20applications&demo-url=https%3A%2F%2Fnextjs-zebedee-starter.vercel.app%2F&demo-image=https%3A%2F%2Fi.imgur.com%2FNf8wRgv.png"> 1-click deploy your own instance. ## Next.js To use ZBD with Next.js and complete this guide, you will need the following: * [ZBD Project with a Live API key](/get-started/api-keys) ### 1. Create a Next.js project Start with a brand new Next.js project. To begin run the following command and walk through the initializing steps: ```bash theme={null} npx create-next-app@latest ``` ### 2. Install @zbdpay/payments-sdk Node.js SDK Install ZBD: ```bash yarn theme={null} yarn add @zbdpay/payments-sdk ``` ```bash npm theme={null} npm install @zbdpay/payments-sdk ``` ```bash pnpm theme={null} pnpm add @zbdpay/payments-sdk ``` ### 3. Create Send and Receive API routes Create a new file `/app/api/receive/route.ts` and add the following code to create an API route that will receive instant Bitcoin payments: ```js theme={null} import { NextResponse } from 'next/server'; import ZbdPayments from '@zbdpay/payments-sdk'; const ZBD_API_KEY = 'b7YW3s2JzZKGcXjIf5Dqof8wjKT2RuWr8'; // dummy key const client = new ZbdPayments({ apikey: ZBD_API_KEY, }); export async function POST() { try { const data = await client.lightningCharges.create({ amount: '100000', callbackUrl: 'https://your-app.com/zbd-callback', description: 'Next.js + ZBD!', }); return NextResponse.json(data); } catch (error) { return NextResponse.json({ error }); } } ``` Then create a new file `/app/api/send/route.ts` and add the following code to create an API route that will send Bitcoin Lightning Address payments: ```js theme={null} import { NextResponse } from 'next/server'; import ZbdPayments from '@zbdpay/payments-sdk'; const ZBD_API_KEY = 'b7YW3s2JzZKGcXjIf5Dqof8wjKT2RuWr8'; const client = new ZbdPayments({ apikey: ZBD_API_KEY, }); export async function POST() { try { const data = await client.lightningAddress.sendPayment({ lnAddress: "andreneves@zbd.gg", // Who is the recipient? amount: "100000", // 100 satoshis (100,000 msats) comment: "Next.js + ZBD!", }); return NextResponse.json(data); } catch (error) { return NextResponse.json({ error }); } } ``` ### 4. Run your Next.js server Run the following command to start your Next.js server: ```bash yarn theme={null} yarn dev ``` ```bash npm theme={null} npm run dev ``` Using an API tool like [Postman](https://postman.com), you should be able to make a POST call to this endpoint and receive a JSON response with a payment request: ```bash theme={null} https://example.com/api/request ``` You're looking for the `data.invoice.request` property in the JSON response. It starts with `lnbc1` and is the payment request anyone in the Bitcoin Lightning Network can use to pay you. ``` lnbc1u1pjdlax9pp5t7jhkd7h2wntd4f2v7xp22dknmjxp0q8nm7hfcny4p7a5mr7x3rsdp9f4hkueteypshggrfde6x2unwv46zqumsv4jkgcqzzsxqzjcsp5dsayu6m6632p28rnkeeqsr7d54amrkv6wh46yrv42gdgca8xl8gs9qyyssqgj2zrkax733rzulfkzc5mqsr8fpwrva82stpa7e0frw32722trv37jlq8mvlqfp8y75lr6mz63zd7qnxar8hhsehuy22pvfq6wjxwqqqa60lx3 ``` Charges and payment requests are usually shown to users as QR codes that can be scanned by mobile apps (e.g. [ZBD](https://zbd.one/download)). Read [Callbacks](/payments/api/callbacks) to understand how to receive updates about your payment asynchronously. Using an API tool like [Postman](https://postman.com), you should be able to make a POST call to this endpoint and receive a JSON response with the payment success message: ```bash theme={null} https://example.com/api/send ``` You're looking for the `status` of `completed` to know that the payment settled successfully. Payments in the Lightning Network are asynchronous so you may see a response stating the payment is `processing`. This is expected -- use the `callbackUrl` property to receive updates about your payments. ### 5. Try it yourself You can now send and receive instant Bitcoin payments using Next.js + ZBD! See the full source code. # Node.js Source: https://docs.zbdpay.com/payments/sdk/typescript/node Learn how to send and receive instant Bitcoin payments with Node.js and ZBD. ## Prerequisites To complete this guide, you will need the following: * [ZBD Project with a Live API key](/get-started/api-keys) ## 1. Create a Node.js project Start with a brand new empty directory called `node-example` and run the following command to initiate a Node.js project: ```bash theme={null} npm init -y ``` ## 2. Install @zbdpay/payments-sdk Node.js SDK Install ZBD: ```bash yarn theme={null} yarn add @zbdpay/payments-sdk ``` ```bash npm theme={null} npm install @zbdpay/payments-sdk ``` ```bash pnpm theme={null} pnpm add @zbdpay/payments-sdk ``` ## 3. Send and receive Bitcoin Create a new file called `index.js` and add the following code: ```js theme={null} import ZbdPayments from '@zbdpay/payments-sdk'; const ZBD_API_KEY = 'b7YW3s2JzZKGcXjIf5Dqof8wjKT2RuWr8'; // dummy key const client = new ZbdPayments({ apikey: ZBD_API_KEY, }); (async function () { try { const data = await client.lightningCharges.create({ amount: '100000', callbackUrl: 'https://your-app.com/zbd-callback', description: 'Node.js + ZBD!', }); console.log(data); } catch (error) { console.error(error); } })(); ``` Run the following command to start your Node.js server: ```bash yarn theme={null} yarn start ``` ```bash npm theme={null} npm start ```
You may need to add a `start` script to your `package.json` file such as `node index.js`. You're looking for the `data.invoice.request` property in the JSON response. It starts with `lnbc1` and is the payment request anyone in the Bitcoin Lightning Network can use to pay you. ``` lnbc1u1pjdlax9pp5t7jhkd7h2wntd4f2v7xp22dknmjxp0q8nm7hfcny4p7a5mr7x3rsdp9f4hkueteypshggrfde6x2unwv46zqumsv4jkgcqzzsxqzjcsp5dsayu6m6632p28rnkeeqsr7d54amrkv6wh46yrv42gdgca8xl8gs9qyyssqgj2zrkax733rzulfkzc5mqsr8fpwrva82stpa7e0frw32722trv37jlq8mvlqfp8y75lr6mz63zd7qnxar8hhsehuy22pvfq6wjxwqqqa60lx3 ``` Charges and payment requests are usually shown to users as QR codes that can be scanned by mobile apps (e.g. [ZBD](https://zbd.one/download)). Read [Callbacks](/payments/api/callbacks) to understand how to receive updates about your payment asynchronously. ## 3. Try it yourself You can now send and receive instant Bitcoin payments using Express.js + ZBD! See the full source code. # Receiving instant Bitcoin payments with Supabase Source: https://docs.zbdpay.com/payments/sdk/typescript/supabase/receive Learn how to receive Bitcoin using Supabase Edge Functions. ## Prerequisites To complete this guide, you will need the following: * [ZBD Project with a Live API key](/get-started/api-keys) * [Supabase account](https://supabase.com/) + [Supabase CLI installed](https://supabase.com/docs/guides/cli#installation) ## 1. Create a Supabase Function To create a Supabase Edge Function you must run the following command locally: ```bash theme={null} supabase functions new zbd-receive ``` This will create a `/supabase/functions/zbd-receive` folder structure in your project. ## 2. Write the Edge Function Use the code below as a starting point for your handler function. You will need to replace the `ZBD_API_KEY` with your own ZBD Project's API key. ```typescript theme={null} import { serve } from "https://deno.land/std@0.168.0/http/server.ts" const ZBD_API_KEY = "b7Ya3s2JZKZcXXX2Dqf8wjKTZZZRuWr8"; const zbdReceive = async (_request: Request): Promise => { const res = await fetch("https://api.zbdpay.com/v0/charges", { method: "POST", headers: { "Content-Type": "application/json", "apikey": `${ZBD_API_KEY}`, }, body: JSON.stringify({ amount: '100000', // 100 satoshis (100,000 msats) -- ~$0.03 description: 'Money at internet speed', // What is this payment request for? }), }); const data = await res.json(); return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json", }, }); }; serve(zbdReceive); ``` ## 3. Receive payment locally To run this Supabase Edge Function locally you can spin up your Supabase instance using the following command. *You may already have this running at this stage.* ```bash theme={null} supabase functions start ``` Then you can run the following command to start your serverless function: ```bash theme={null} supabase functions serve zbd-receive --no-verify-jwt ``` Note that we add the `--no-verify-jwt` flag to the command to disable JWT verification. This is because we are not passing a JWT token to the function when we call it. **This is not recommended for production use.** Supabase CLI will output a HTTP URL endpoint that you can use to test your function by issuing a POST request to that resource. ## 4. Deploying to Supabase Edge After testing it locally, you can deploy your function to Supabase Edge using the following command. *You will need to enter your Supabase project ID to deploy.* ```bash theme={null} supabase functions deploy zbd-receive ``` Once you deploy you will receive a URL that you can use to view that function in production on Supabase's Web Dashboard: Supabase Edge Functions dashboard Opening your browser (or another HTTP client) to the URL provided by Supabase: `https://xxxxxxxxxxxxx.supabase.co/functions/v1/zbd-receive` should return a JSON response with the payment request details. You can also test this using curl command: ```bash theme={null} curl https://xxxxxxxxxxxxx.supabase.co/functions/v1/zbd-receive ``` You're looking for the `data.invoice.request` property in the JSON response. It starts with `lnbc1` and is the payment request anyone in the Bitcoin Lightning Network can use to pay you. ``` lnbc1u1pjdlax9pp5t7jhkd7h2wntd4f2v7xp22dknmjxp0q8nm7hfcny4p7a5mr7x3rsdp9f4hkueteypshggrfde6x2unwv46zqumsv4jkgcqzzsxqzjcsp5dsayu6m6632p28rnkeeqsr7d54amrkv6wh46yrv42gdgca8xl8gs9qyyssqgj2zrkax733rzulfkzc5mqsr8fpwrva82stpa7e0frw32722trv37jlq8mvlqfp8y75lr6mz63zd7qnxar8hhsehuy22pvfq6wjxwqqqa60lx3 ``` Charges and payment requests are usually shown to users as QR codes that can be scanned by mobile apps (e.g. [ZBD](https://zbd.one/download)). Read [Callbacks](/payments/api/callbacks) to understand how to receive updates about your payment asynchronously. ## 5. Try it yourself You can now begin receiving instant Bitcoin payments on the edge with Supabase + ZBD! See the full source code. # Sending instant Bitcoin payments with Supabase Source: https://docs.zbdpay.com/payments/sdk/typescript/supabase/send Learn how to send Bitcoin using Supabase Edge Functions. ## Prerequisites To complete this guide, you will need the following: * [ZBD Project with a Live API key](/get-started/api-keys) * [Supabase account](https://supabase.com/) + [Supabase CLI installed](https://supabase.com/docs/guides/cli#installation) ## 1. Create a Supabase Function To create a Supabase Edge Function you must run the following command locally: ```bash theme={null} supabase functions new zbd-send ``` This will create a `/supabase/functions/zbd-send` folder structure in your project. ## 2. Write the Edge Function Use the code below as a starting point for your handler function. You will need to replace the `ZBD_API_KEY` with your own ZBD Project's API key. ```typescript theme={null} import { serve } from "https://deno.land/std@0.168.0/http/server.ts" const ZBD_API_KEY = "b7Ya3s2JZKZcXXX2Dqf8wjKTZZZRuWr8"; const zbdSend = async (_request: Request): Promise => { const res = await fetch("https://api.zbdpay.com/v0/ln-address/send-payment", { method: "POST", headers: { "Content-Type": "application/json", "apikey": `${ZBD_API_KEY}`, }, body: JSON.stringify({ lnAddress: 'andre@zbd.gg', // Who is the recipient of the payment? amount: '100000', // 100 satoshis (100,000 msats) -- ~$0.03 comment: 'Money at internet speed', // What is this payment for? }), }); const data = await res.json(); return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json", }, }); }; serve(zbdSend); ``` ## 3. Send payment locally In order to successfully send payments through the API, you must have an active balance in the ZBD Project you are using. [Learn more about depositing funds into a ZBD Project wallet](/get-started/add-funds). If you do not have funds in the ZBD Project you are using, you will receive a 4xx error from the API stating insufficient funds. To run this Supabase Edge Function locally you can spin up your Supabase instance using the following command. *You may already have this running at this stage.* ```bash theme={null} supabase functions start ``` Then you can run the following command to start your serverless function: ```bash theme={null} supabase functions serve zbd-send --no-verify-jwt ``` Note that we add the `--no-verify-jwt` flag to the command to disable JWT verification. This is because we are not passing a JWT token to the function when we call it. **This is not recommended for production use.** Supabase CLI will output a HTTP URL endpoint that you can use to test your function by issuing a POST request to that resource. ## 4. Deploying to Supabase Edge After testing it locally, you can deploy your function to Supabase Edge using the following command. *You will need to enter your Supabase project ID to deploy.* ```bash theme={null} supabase functions deploy zbd-send ``` Once you deploy you will receive a URL that you can use to view that function in production on Supabase's Web Dashboard: Supabase Edge Functions dashboard Opening your browser (or another HTTP client) to the URL provided by Supabase: `https://xxxxxxxxxxxxx.supabase.co/functions/v1/zbd-send` should return a JSON response with the payment sent message. You can also test this using curl command: ```bash theme={null} curl https://xxxxxxxxxxxxx.supabase.co/functions/v1/zbd-send ``` You're looking for the `status` of `completed` to know that the payment settled successfully. Payments in the Lightning Network are asynchronous so you may see a response stating the payment is `processing`. This is expected -- use the `callbackUrl` property to receive updates about your payments. ## 5. Try it yourself You can now begin sending instant Bitcoin payments on the edge with Supabase + ZBD! See the full source code. # Receiving instant Bitcoin payments on Vercel Source: https://docs.zbdpay.com/payments/sdk/typescript/vercel/receive Learn how to receive Bitcoin using Vercel Edge Functions. ## Prerequisites To complete this guide, you will need the following: * [ZBD Project with a Live API key](/get-started/api-keys) * [Vercel account](https://vercel.com/signup) + [Vercel CLI installed](https://vercel.com/docs/cli#installing-vercel-cli) ## 1. Create a Next.js Project Follow the prompts on the create-next-app CLI to create a new project, and then change into the directory of your project. ```bash theme={null} yarn create next-app ``` ```bash theme={null} cd ``` ## 2. Write an Edge Function For this guide we will be making use of the [App Router](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) inside of Next.js. Create a new file in the `app/api/request/route.ts` that creates a Bitcoin Lightning Charge (payment request) with the following code: ```javascript app/api/request/route.ts theme={null} import { NextResponse } from 'next/server'; export const runtime = 'edge'; export const dynamic = 'force-dynamic'; const ZBD_BASE_URL = 'https://api.zbdpay.com'; const ZBD_API_KEY = 'b7YW3s2JzZKGcXjIf5Dqof8wjKT2RuWr8'; export async function GET() { const res = await fetch(`${ZBD_BASE_URL}/v0/charges`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'apikey': `${ZBD_API_KEY}`, }, body: JSON.stringify({ amount: '100000', // 100 satoshis (100,000 msats) -- ~$0.03 description: 'Money at internet speed', // What is this payment request for? }), }); if (res.ok) { const data = await res.json(); return NextResponse.json(data); } } ``` ## 3. Create payment request locally Run function locally: ```bash theme={null} npx next dev ``` Opening your browser to the following URL: `https://example.com/api/request` should return a JSON response with a payment request. You can also test this using curl command: ```bash theme={null} curl https://example.com/api/request ``` You're looking for the `data.invoice.request` property in the JSON response. It starts with `lnbc1` and is the payment request anyone in the Bitcoin Lightning Network can use to pay you. ``` lnbc1u1pjdlax9pp5t7jhkd7h2wntd4f2v7xp22dknmjxp0q8nm7hfcny4p7a5mr7x3rsdp9f4hkueteypshggrfde6x2unwv46zqumsv4jkgcqzzsxqzjcsp5dsayu6m6632p28rnkeeqsr7d54amrkv6wh46yrv42gdgca8xl8gs9qyyssqgj2zrkax733rzulfkzc5mqsr8fpwrva82stpa7e0frw32722trv37jlq8mvlqfp8y75lr6mz63zd7qnxar8hhsehuy22pvfq6wjxwqqqa60lx3 ``` Charges and payment requests are usually shown to users as QR codes that can be scanned by mobile apps (e.g. [ZBD](https://zbd.one/download)). Read [Callbacks](/payments/api/callbacks) to understand how to receive updates about your payment asynchronously. ## 4. Create payment request in production Deploy your project to Vercel: ```bash theme={null} vercel ``` Opening your browser to the following URL: `https://project-name.vercel.app/api/request` should now return this in production. You can also test this using curl command: ```bash theme={null} curl https://project-name.vercel.app/api/request ``` ## 5. Try it yourself You can now begin receiving instant Bitcoin payments on the edge with Vercel + ZBD! See the full source code. # Sending instant Bitcoin payments on Vercel Source: https://docs.zbdpay.com/payments/sdk/typescript/vercel/send Learn how to send Bitcoin using Vercel Edge Functions. ## Prerequisites To complete this guide, you will need the following: * [ZBD Project with a Live API key](/get-started/api-keys) * [Vercel account](https://vercel.com/signup) + [Vercel CLI installed](https://vercel.com/docs/cli#installing-vercel-cli) ## 1. Create a Next.js Project Follow the prompts on the create-next-app CLI to create a new project, and then change into the directory of your project. ```bash theme={null} yarn create next-app ``` ```bash theme={null} cd ``` ## 2. Write an Edge Function For this guide we will be making use of the [App Router](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) inside of Next.js. Create a new file in the `app/api/send/route.ts` that makes a payment to a [Lightning Address](/payments/glossary#lightning-address) (e.g. [andre@zbd.gg](mailto:andre@zbd.gg)) with the following code: ```javascript app/api/send/route.ts theme={null} import { NextResponse } from 'next/server'; export const runtime = 'edge'; export const dynamic = 'force-dynamic'; const ZBD_BASE_URL = 'https://api.zbdpay.com'; const ZBD_API_KEY = 'b7YW3s2JzZKGcXjIf5Dqof8wjKT2RuWr8'; export async function GET() { const res = await fetch(`${ZBD_BASE_URL}/v0/ln-address/send-payment`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'apikey': `${ZBD_API_KEY}`, }, body: JSON.stringify({ lnAddress: 'andre@zbd.gg', // Who is the recipient of the payment? amount: '100000', // 100 satoshis (100,000 msats) -- ~$0.03 comment: 'Money at internet speed', // What is this payment for? }), }); if (res.ok) { const data = await res.json(); return NextResponse.json(data); } } ``` ## 3. Send payment locally In order to successfully send payments through the API, you must have an active balance in the ZBD Project you are using. [Learn more about depositing funds into a ZBD Project wallet](/get-started/add-funds). If you do not have funds in the ZBD Project you are using, you will receive a 4xx error from the API. Run function locally: ```bash theme={null} npx next dev ``` Opening your browser to the following URL: `https://example.com/api/send` should return a JSON response with the payment sent message. You can also test this using curl command: ```bash theme={null} curl https://example.com/api/send ``` You're looking for the `status` of `completed` to know that the payment settled successfully. Payments in the Lightning Network are asynchronous so you may see a response stating the payment is `processing`. This is expected -- use the `callbackUrl` property to receive updates about your payments. ## 4. Send payment in production Deploy your project to Vercel: ```bash theme={null} vercel ``` Opening your browser to the following URL: `https://project-name.vercel.app/api/send` should now return this in production. You can also test this using curl command: ```bash theme={null} curl https://project-name.vercel.app/api/send ``` ## 5. Try it yourself You can now begin sending instant Bitcoin payments on the edge with Vercel + ZBD! See the full source code. # ZBD Widget Source: https://docs.zbdpay.com/widget Embed a hosted cashout experience so your users can withdraw earned balances to bank accounts, Cash App, and more. ## Overview The ZBD Widget is an embeddable iframe that runs the cashout experience for your users โ€” OTP verification, identity verification, bank account linking, and bank payouts. Your backend creates widget users, funds their point balances, creates sessions, and handles its own end-user terms and disclosures before the widget opens. ## Prerequisites Before integrating: 1. Make sure you already have a [ZBD Developer Dashboard account](/get-started/create-account) and a [project](/get-started/create-project). 2. Copy the project API key from the project's [API section](/get-started/api-keys). 3. Open the **Widget** tab in the Developer Dashboard to configure sandbox balance and webhook settings. 4. If the Widget tab is not available yet, contact ZBD support to enable it for your project. After that, fund the sandbox account before you try to cash out. ## Quick Start To get sandbox running quickly, start with the [sandbox guide](/widget/sandbox): 1. Create a sandbox user. 2. Fund the sandbox user. 3. Create a widget session. 4. Embed the returned `widget_url` in your frontend. 5. Listen for widget events and webhook deliveries. ## Terms and disclosures Your application must present and record acceptance of its applicable Terms of Service, Privacy Policy, and required disclosures before it creates a widget session. The widget does not render those documents or collect that acceptance on the publisher's behalf, but it validates that the required terms have been accepted before the user can access its services. ## Country availability The widget uses the country resolved from the user's IP before identity verification. After identity verification, it uses the user's verified residential country to select the available bank rail. ### USD bank cashout USD bank cashout (ACH) is available only in the United States. ### EUR bank cashout EUR bank cashout (SCT) is available in Austria, Belgium, Denmark, Estonia, Finland, France, Germany, Italy, Latvia, Lithuania, the Netherlands, Poland, Portugal, Spain, and Sweden. SCT is not available in other countries, including Bulgaria, Croatia, Cyprus, Czechia, Greece, Hungary, Ireland, Luxembourg, Malta, Romania, Slovakia, Slovenia, and Switzerland. ### Restricted locations The widget is not supported for users in Cuba, Iran, North Korea, or Syria. ## Integration Flow ``` 1. Create User POST /api/v1/widget/users (your server โ†’ ZBD API) 2. Fund User POST /api/v1/widget/users/fund (your server โ†’ ZBD API) 3. Reverse a Fund POST /api/v1/widget/users/deplete (your server โ†’ ZBD API) 4. Get Balance GET /api/v1/widget/users/{userId}/balance (your server โ†’ ZBD API) 5. Create Session POST /api/v1/widget/users/session (your server โ†’ ZBD API) 6. Embed Widget ``` Use a native WebView package for your Unity target and load the `widget_url` returned by your backend. ```csharp theme={null} using UnityEngine; public class ZbdWidgetLauncher : MonoBehaviour { [SerializeField] private GameObject webViewContainer; public void OpenWidget(string widgetUrl) { // Replace this with your Unity WebView package API. var webView = webViewContainer.GetComponent(); webView.LoadUrl(widgetUrl); webView.SetVisible(true); } } public interface IWebView { void LoadUrl(string url); void SetVisible(bool visible); } ``` Enable Unreal's **Web Browser** plugin, add a Web Browser widget to your UMG screen, and load the `widget_url` returned by your backend. ```cpp theme={null} #include "Components/WebBrowser.h" void UCashoutScreen::OpenZbdWidget(const FString& WidgetUrl) { if (ZbdWidgetBrowser) { ZbdWidgetBrowser->LoadURL(WidgetUrl); } } ``` **Allow document downloads.** The widget lets users download documents (disclosure PDFs, cashout receipts/statements). Browsers only permit a framed page to start a download if the iframe explicitly allows it. If you apply a `sandbox` attribute โ€” as in the example above โ€” it **must** include **`allow-downloads`** (alongside `allow-scripts allow-same-origin allow-forms allow-popups`). Without `allow-downloads`, the browser blocks the in-frame download and the widget falls back to opening the document in a **new browser tab**. If you do not set a `sandbox` attribute at all, downloads work by default. ## Embed Parameters Pass these as URL query parameters on the widget URL: | Parameter | Required | Description | | --------------- | -------- | --------------------------------------------------------------- | | `session_token` | Yes | JWT from Create Session | | `flow` | No | `cashout` (default), `kyc`, `add-method` | | `theme` | No | `zbd-default`, `zbd-light` | | `embed` | No | `true` for chrome-less mode (no header/footer) | | `component` | No | `balance`, `history`, `method-picker` for standalone components | # Browser Events Source: https://docs.zbdpay.com/widget/browser-events Reference for ZBD Widget iframe events sent to your frontend with postMessage. ## Overview The ZBD Widget communicates with your page from the iframe using browser `postMessage` events. Use these events for frontend state such as widget readiness, close actions, cashout submission, identity verification, and linked payout methods. Server-side cashout status updates are delivered separately as signed webhooks. See [Server Webhooks](/widget/webhooks). ## Listening for Events Listen for events on the parent window: ```javascript theme={null} window.addEventListener("message", (event) => { const { type, payload } = event.data ?? {}; if (!type?.startsWith("ZBD_")) return; switch (type) { case "ZBD_WIDGET_READY": console.log("Widget loaded", payload?.user); break; case "ZBD_CASHOUT_SUCCESS": console.log("Cashout submitted", payload); // Close the widget iframe, refresh balances break; case "ZBD_WIDGET_CLOSE": // User closed the widget โ€” remove the iframe break; } }); ``` ## Event Types | Event | Payload | Description | | ---------------------- | ------------------------------------------------------- | -------------------------------- | | `ZBD_WIDGET_READY` | `{ user? }` | Widget loaded and authenticated | | `ZBD_WIDGET_CLOSE` | โ€” | User closed the widget | | `ZBD_CASHOUT_SUCCESS` | `{ cashout_id, amount, currency_code, usd_equivalent }` | Cashout submitted successfully | | `ZBD_KYC_COMPLETE` | `{ tier }` | Identity verification approved | | `ZBD_METHOD_ADDED` | `{ payout_method_id, type, label }` | Bank account linked via Plaid | | `ZBD_AUTH_TOKEN_READY` | `{ auth_token, user? }` | OTP verified, auth JWT available | | `ZBD_WIDGET_ERROR` | `{ code, message }` | An error occurred | # Create Session Source: https://docs.zbdpay.com/widget/create-session POST https://api.zbdpay.com/api/v1/widget/users/session Mint a widget session and get the embeddable URL. ## Description Creates a widget session for a user. Returns a `session_token` (JWT) and a `widget_url` that you embed as an iframe. The session is valid for the duration specified in `expires_at`. The widget handles OTP email verification, identity verification (Onfido), bank linking (Plaid), and cashout initiation internally. Before creating a session, your application must already have presented and recorded the user's acceptance of its applicable Terms of Service, Privacy Policy, and required disclosures. The widget does not display or collect those acceptances for the publisher. The session can report outstanding disclosures as a read-only status. It does not render them or collect acceptance. Cashout-specific disclosures are enforced when a cashout is submitted. See [Disclosure Agreements](/widget/disclosures). ## Configuration ### Header Parameters Your ZBD project API key (production scope). Content Type ### Body Parameters User's email address. Required when `access_token` is not supplied. Optional HTTPS server URL to receive widget webhook events. Optional publisher reference for the session. Optional key-value metadata to attach to the session. Existing access token. When supplied, `email` is not required and OTP is skipped. ```bash cURL theme={null} curl -X POST https://api.zbdpay.com/api/v1/widget/users/session \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "email": "player@example.com", "webhook_url": "https://your-server.com/zbd/webhooks", "reference_id": "player-42", "metadata": { "source": "your-game" } }' ``` ```json 201 theme={null} { "success": true, "data": { "session_token": "eyJhbGciOiJSUzI1NiIs...", "expires_at": "2026-06-01T00:00:00.000Z", "widget_url": "https://widget.zbd.gg?session_token=eyJhbGci..." } } ``` # Create User Source: https://docs.zbdpay.com/widget/create-user POST https://api.zbdpay.com/api/v1/widget/users Create or fetch a publisher-linked widget user. ## Description Provisions a user in ZBD for your project. Idempotent โ€” if a user with the same `reference_id` or `email` already exists for your project, the existing user is returned. Call this from your server before creating a widget session. Some cashout methods require acceptance of the latest disclosure before they can proceed. When `outstanding_disclosures` is not empty, your application must present those documents and record acceptance before creating a widget session. See [Disclosure Agreements](/widget/disclosures). ## Configuration ### Header Parameters Your ZBD project API key (production scope). Content Type ### Body Parameters Your stable internal user identifier. Must be unique per project. User's email address. Used for OTP verification in the widget. ```bash cURL theme={null} curl -X POST https://api.zbdpay.com/api/v1/widget/users \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reference_id": "player-42", "email": "player@example.com" }' ``` ## Response The response `data` includes the new (or existing) user plus an `outstanding_disclosures` array โ€” the disclosure versions this user still needs to accept. This is returned on the **first user request**, so you have the user's outstanding disclosures immediately, with no extra call. Disclosure versions the user has not yet accepted. Empty when the user is current on all disclosures. Each entry includes `id`, `type_id`, `name`, `description`, `version`, `content_uri`, `created_at`, and `due_date`. `outstanding_disclosures` lists only what is still outstanding. To list a user's full acceptance state (including what they have **already accepted**), call [Get Disclosure Status](/widget/get-disclosure-status) at any time. See [Disclosure Agreements](/widget/disclosures) for the full model. ```json 201 theme={null} { "success": true, "data": { "id": "4ac4fd8a-cc2c-4d03-af09-a76f4e89d652", "reference_id": "player-42", "email": "player@example.com", "outstanding_disclosures": [ { "id": 12, "type_id": 2, "name": "Terms of Service", "description": "ZBD Terms of Service Document.", "version": "1.4.0", "content_uri": "https://...", "created_at": "2026-06-24T20:11:45Z", "due_date": "2025-08-11T00:00:00" } ] } } ``` # Deplete User Source: https://docs.zbdpay.com/widget/deplete-user POST https://api.zbdpay.com/api/v1/widget/users/deplete Debit USDZ points from a widget user's point balance. ## Description Reverse an available fund by debiting publisher points from a widget user's point balance and returning them to the project's configured sink/source account. This creates a new debit; it does not modify the original fund transfer. Amount is in **currency minor units**. For USDZ, `100` equals `$1.00` worth of points. This endpoint is idempotent on `idempotency_key`. The user must already have enough point balance for the debit. ## Configuration ### Header Parameters Your ZBD project API key (production scope). Content Type ### Body Parameters The ZBD user ID returned by Create User. Amount in currency minor units. For USDZ: `100` = $1.00, `1000` = $10.00. Point currency code. Use `USDZ` unless your project has a different configured point currency. Unique key to prevent duplicate transfers. Use a UUID. ```bash cURL theme={null} curl -X POST https://api.zbdpay.com/api/v1/widget/users/deplete \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "4ac4fd8a-cc2c-4d03-af09-a76f4e89d652", "amount": 500, "currency": "USDZ", "idempotency_key": "551d1b1b-c387-4e16-9a7a-c15be30d0d62" }' ``` ```json 200 theme={null} { "success": true, "message": "Point transfer completed successfully.", "data": { "transfer_id": "f4ba94c1-1234-5678-abcd-ef0123456789", "status": "COMPLETED" }, "error": null } ``` ```json 400 theme={null} { "success": false, "message": "Insufficient point balance.", "data": null, "error": { "code": "TX3017", "message": "Insufficient point balance." } } ``` # Disclosure Agreements Source: https://docs.zbdpay.com/widget/disclosures Understand how widget user disclosure acceptance works before cashout. ## Overview Some cashout flows require the user to accept the latest legal disclosure before continuing. ZBD tracks acceptance by disclosure type and version, so a user may need to accept a new version even if they accepted an older version before. Your application is responsible for presenting the documents and collecting the user's acceptance before it creates a widget session. The widget does not render Terms of Service, Privacy Policy, or other disclosure documents, or record acceptance on the publisher's behalf. It validates that the required terms have been accepted before the user can access its services. ## End-to-end Flow ``` 1. Your server creates or resolves the widget user. 2. Your server reads `outstanding_disclosures` from the response or calls Get Disclosure Status. 3. Your application presents each outstanding document to the user outside the widget. 4. Your server records acceptance with Submit Disclosure Acceptance. 5. Your server creates a widget session. 6. Your frontend opens the returned `widget_url`. 7. The user can continue to bank linking, identity verification, and cashout. ``` Do not open a widget session until the publisher-side disclosure flow is complete. The disclosure APIs require your server-side API key and must not be called from a browser, game client, or WebView. ## Outstanding Disclosures on User Creation You do not need a separate call to discover what a user still owes. The [Create User](/widget/create-user) response (`POST /api/v1/widget/users`) already includes an **`outstanding_disclosures`** array on that **first user request** โ€” the disclosure versions this user has not yet accepted. Surface or record them right away; if the array is empty, the user is current. `outstanding_disclosures` only lists what is **still outstanding**. When you need the complete picture โ€” including which disclosures a user has **already accepted** and when โ€” use the dedicated endpoints: * [Get Disclosure Status](/widget/get-disclosure-status) โ€” list every current disclosure type with the user's acceptance state (`tos_current`, `accepted_at`), so you can show accepted vs. outstanding. * [Submit Disclosure Acceptance](/widget/submit-disclosure-acceptance) โ€” record acceptance of the latest version from your server. The same `outstanding_disclosures` shape also appears in the widget session status during a session (see [Session Status](#session-status)). Create User is the initial snapshot at provisioning time; Get Disclosure Status is the source of truth for the full, up-to-date acceptance state. ## Disclosure Types The widget disclosure endpoints can check and record acceptance for all current disclosure types. | Disclosure type | Type ID | Used for | | ------------------------- | ------: | ----------------------------- | | Terms of Service | `2` | General ZBD terms | | Privacy Policy | `3` | General privacy disclosure | | Electronic Funds Transfer | `6` | ACH bank payout authorization | ACH cashout submission requires the current Electronic Funds Transfer disclosure to be accepted. If the user has not accepted the latest Electronic Funds Transfer disclosure, the cashout request is rejected until acceptance is recorded. ## Session Status During a widget session, ZBD can return outstanding disclosure information as a read-only status. The field is named `outstanding_disclosures`. This does not cause the widget to display the documents or collect acceptance. Terms of Service and Privacy Policy are expected to be accepted before session creation. Cashout-specific checks, such as Electronic Funds Transfer acceptance for ACH, are enforced when the user submits the cashout. Example session status shape: ```json theme={null} { "success": true, "data": { "id": "4ac4fd8a-cc2c-4d03-af09-a76f4e89d652", "email": "player@example.com", "is_id_verified": true, "kyc_tier": 2, "kyc_status": "approved", "capabilities": [], "outstanding_disclosures": [ { "id": 12, "type_id": 2, "name": "Terms of Service", "description": "ZBD Terms of Service Document.", "version": "1.4.0", "content_uri": "https://...", "created_at": "2026-06-24T20:11:45Z", "due_date": "2025-08-11T00:00:00" } ] }, "message": "Session account status retrieved successfully.", "error": null } ``` If `outstanding_disclosures` is empty, the user is current on all session disclosures returned by this endpoint. If it is not empty, return the user to the publisher-side acceptance flow rather than expecting the widget to collect it. ## Recording acceptance before the widget Use the widget disclosure APIs from your server before opening the widget. The API key determines the project context, so these endpoints only require the widget user ID in the path. Disclosure endpoints require your server-side API key. Do not call these endpoints directly from a browser, mobile client, game client, or WebView. Use [Get Disclosure Status](/widget/get-disclosure-status) to check current disclosure acceptance and [Submit Disclosure Acceptance](/widget/submit-disclosure-acceptance) to record acceptance for the latest version of one or more disclosure types. ## Existing Users Disclosure acceptance is versioned. If a user already exists and needs to accept a current disclosure, submit the acceptance with the disclosure endpoint above. Do not rely on an idempotent user-create call to update disclosure acceptance for an existing user. User creation can return an existing user without recording new disclosure acceptance. ## Sandbox Sandbox uses the same disclosure status and acceptance model as production. When testing disclosure handling, use your sandbox API key and sandbox API base URL: ```text theme={null} https://sandbox-api.zbdpay.com ``` Some sandbox bypass settings can skip cashout checks for faster testing. If you specifically need to verify cashout disclosure blocking behavior, make sure your sandbox setup is not bypassing the cashout disclosure gate. # Fund User Source: https://docs.zbdpay.com/widget/fund-user POST https://api.zbdpay.com/api/v1/widget/users/fund Credit USDZ points to a widget user's point balance. ## Description Credit publisher points to a widget user's point balance. Amount is in **currency minor units**. For USDZ, `100` equals `$1.00` worth of points. This endpoint is idempotent on `idempotency_key`. ## Reverse a fund To take back points that you previously funded, call [`POST /api/v1/widget/users/deplete`](/widget/deplete-user). This creates a new debit from the user's currently available point balance back to your configured sink/source account; it does not undo or mutate the original fund transfer. Use a new `idempotency_key` for the deplete request. A deplete can only succeed while the user has enough available points. It cannot recover points that have already been spent or cashed out. ## Configuration ### Header Parameters Your ZBD project API key (production scope). Content Type ### Body Parameters The ZBD user ID returned by Create User. Amount in currency minor units. For USDZ: `100` = $1.00, `1000` = $10.00. Point currency code. Use `USDZ` unless your project has a different configured point currency. Unique key to prevent duplicate transfers. Use a UUID. ```bash cURL theme={null} curl -X POST https://api.zbdpay.com/api/v1/widget/users/fund \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "4ac4fd8a-cc2c-4d03-af09-a76f4e89d652", "amount": 1000, "currency": "USDZ", "idempotency_key": "90f9371e-c48a-4bc9-9075-ef7a4c6816ec" }' ``` ```json 200 theme={null} { "success": true, "message": "Point transfer completed successfully.", "data": { "transfer_id": "f4ba94c1-1234-5678-abcd-ef0123456789", "status": "COMPLETED" }, "error": null } ``` # Get User Balance Source: https://docs.zbdpay.com/widget/get-balance GET https://api.zbdpay.com/api/v1/widget/users/{user_id}/balance Retrieve a widget user's available and total reward balance. ## Description Returns the user's widget reward balance for your project. Use this from your server when you need to show a user's cashout-ready balance outside the widget. Amounts are returned in **currency minor units**. For USD, `100` equals `$1.00`. ## Configuration ### Header Parameters Your ZBD project API key (production scope). ### Path Parameters The ZBD user ID returned by Create User. ```bash cURL theme={null} curl https://api.zbdpay.com/api/v1/widget/users/4ac4fd8a-cc2c-4d03-af09-a76f4e89d652/balance \ -H "apikey: YOUR_API_KEY" ``` ```json 200 theme={null} { "userId": "4ac4fd8a-cc2c-4d03-af09-a76f4e89d652", "balances": [ { "currency": "USD", "availableBalance": 5000, "totalBalance": 5000 } ] } ``` # Get Disclosure Status Source: https://docs.zbdpay.com/widget/get-disclosure-status GET https://api.zbdpay.com/api/v1/widget/users/{userId}/disclosures Check a widget user's current disclosure acceptance status. ## Description Returns the user's acceptance status for each current disclosure type. Use this from your server to determine which current disclosures the user must accept. Present any outstanding documents and collect the user's acceptance in your application before creating a widget session, then record it with [Submit Disclosure Acceptance](/widget/submit-disclosure-acceptance). Do not call this endpoint from a browser, mobile client, game client, or WebView. The API key determines the publisher/project context. `userId` is the ZBD user ID returned when the widget user is created or resolved. ## Configuration ### Header Parameters Your ZBD project API key. ### Path Parameters The ZBD user ID returned when the widget user is created or resolved. ```bash cURL theme={null} curl https://api.zbdpay.com/api/v1/widget/users/{userId}/disclosures \ -H "apikey: YOUR_API_KEY" ``` ```json 200 theme={null} { "success": true, "message": "Publisher user ToS status retrieved successfully.", "data": [ { "type_id": 2, "name": "Terms of Service", "version": "1.4.0", "tos_current": true, "accepted_at": "2026-01-15T12:00:00Z" }, { "type_id": 6, "name": "Electronic Funds Transfer", "version": "1.0.0", "tos_current": false, "accepted_at": null } ], "error": null } ``` `tos_current` means the user has accepted the current version of that disclosure type. A value of `false` means there is no acceptance on record, or the user's latest acceptance is for an older version. ## Errors | Status | Reason | | ------ | ------------------------------------------------------------------- | | `401` | Missing or invalid API key | | `403` | The API key is not allowed to access widget disclosure APIs | | `404` | `userId` does not belong to the project associated with the API key | # Get User Transactions Source: https://docs.zbdpay.com/widget/get-transactions GET https://api.zbdpay.com/api/v1/widget/users/{user_id}/transactions Retrieve a paginated transaction history for a widget user. ## Description Returns a widget user's transaction history for your project. Use this from your server when you need to show reward funding and cashout activity outside the widget. Amounts are returned in **currency minor units**. For USD, `100` equals `$1.00`. ## Configuration ### Header Parameters Your ZBD project API key (production scope). ### Path Parameters The ZBD user ID returned by Create User. ### Query Parameters Number of transactions to skip. Defaults to `0`. Maximum number of transactions to return. Defaults to `20`; maximum is `100`. Optional transaction type filter. Optional ISO 8601 start date filter. Optional ISO 8601 end date filter. ```bash cURL theme={null} curl "https://api.zbdpay.com/api/v1/widget/users/4ac4fd8a-cc2c-4d03-af09-a76f4e89d652/transactions?skip=0&limit=20" \ -H "apikey: YOUR_API_KEY" ``` ```json 200 theme={null} { "userId": "4ac4fd8a-cc2c-4d03-af09-a76f4e89d652", "transactions": [ { "id": "f4ba94c1-1234-5678-abcd-ef0123456789", "type": "reward", "status": "completed", "description": "Level 37 reward", "amount": 1000, "currency": "USD", "fee": 0, "counterparty": "Game Rewards", "createdAt": "2026-05-28T22:21:03.067Z" } ], "total": 1, "skip": 0, "limit": 20 } ``` # Get User Source: https://docs.zbdpay.com/widget/get-user GET https://api.zbdpay.com/api/v1/widget/users/{user_id} Retrieve a widget user's details and verification status. ## Description Returns the user's details including their current identity verification status. Use this to check if a user has completed identity verification before showing cashout options. ## Configuration ### Header Parameters Your ZBD project API key (production scope). ### Path Parameters The ZBD user ID returned by Create User. ```bash cURL theme={null} curl https://api.zbdpay.com/api/v1/widget/users/4ac4fd8a-cc2c-4d03-af09-a76f4e89d652 \ -H "apikey: YOUR_API_KEY" ``` ```json 200 theme={null} { "success": true, "data": { "id": "4ac4fd8a-cc2c-4d03-af09-a76f4e89d652", "reference_id": "player-42", "email": "player@example.com", "is_id_verified": true } } ``` # Sandbox Source: https://docs.zbdpay.com/widget/sandbox Test the full ZBD Widget flow without real money, real identity verification, or real bank transfers. ## Overview Every ZBD project includes a **sandbox environment** for testing the ZBD Widget integration end-to-end. Sandbox mode simulates the entire flow โ€” funding users, initiating cashouts, and receiving webhooks โ€” without moving real money through ACH or requiring real identity verification. **Sandbox API keys** are separate from production keys. Make sure you're using the right key for the right environment. **Sandbox API base URL:** `https://sandbox-api.zbdpay.com` ## What Sandbox Does | Feature | Sandbox | Production | | ------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------- | | **User creation** | Creates sandbox-scoped widget users | Creates production widget users | | **Balance funding** | Credits test reward balance; no live pool is debited | Transfers publisher points from your live pool | | **Identity verification** | Controlled through sandbox identity verification status helpers; no live Onfido review | Required for bank cashouts | | **Bank linking (Plaid)** | Full Plaid sandbox flow | Real bank connections | | **ACH payout** | Simulated when sandbox bypass is enabled; no real transfer | Real ACH via banking partner | | **Disclosures** | Same status and acceptance model; bypass settings may skip cashout disclosure checks | Required disclosures can block cashout until accepted | | **Webhooks** | Fire normally to your `webhook_url` | Same | | **Widget UI** | Identical to production | Same | ## Getting Started ### 1. Get Your Sandbox API Key In the [ZBD Developer Dashboard](https://dashboard.zebedee.io), navigate to your project's **API** tab and copy the **Sandbox** key. Never use sandbox keys in production or production keys in sandbox. The endpoints will reject mismatched keys. ### 2. Create a Test User ```bash theme={null} curl -X POST https://sandbox-api.zbdpay.com/api/v1/cashout/sandbox/users \ -H "apikey: YOUR_SANDBOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reference_id": "test-player-1", "email": "test@yourcompany.com" }' ``` Sandbox user creation uses a sandbox-only endpoint. Use the returned `data.id` as `zbd_user_id` when funding the user and as the user ID when setting identity verification status. Use `POST /api/v1/widget/users` only for production widget users. ### 3. Fund the Test User ```bash theme={null} curl -X POST https://sandbox-api.zbdpay.com/api/v1/widget/users/fund \ -H "apikey: YOUR_SANDBOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "USER_ID_FROM_STEP_2", "amount": 5000, "currency": "USDZ", "idempotency_key": "UNIQUE_UUID" }' ``` In sandbox, funding transfers don't debit a real pool. The balance is credited for testing purposes. ### 4. Create a Widget Session ```bash theme={null} curl -X POST https://sandbox-api.zbdpay.com/api/v1/widget/users/session \ -H "apikey: YOUR_SANDBOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "email": "test@yourcompany.com", "webhook_url": "https://your-server.com/zbd/webhooks" }' ``` ### 5. Open the Widget Embed the returned `widget_url` in an iframe. In sandbox, the URL should point at `https://widget.sandbox.zbd.gg`. The widget behaves identically to production โ€” same UI, same flows โ€” but cashouts complete without real bank transfers. When you reach bank linking in sandbox, you can use the Plaid sandbox institution **Platypus First Bank** with the credentials `user_good` / `pass_good`. If Plaid asks for a phone number step, you can bypass it in the sandbox flow. When you reach identity verification in sandbox, use a **US address** so the approval path can complete successfully. ## Sandbox Controls Use these sandbox-only controls on `https://sandbox-api.zbdpay.com` with `apikey: YOUR_SANDBOX_API_KEY` to put a test user or monetary account into a known state before opening the widget. For user rewards, use `POST /api/v1/widget/users/fund` and `POST /api/v1/widget/users/deplete`. The monetary-account helpers below are for state setup when you already have the monetary account ID. | Control | Endpoint | Request body | | ------------------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------- | | Create sandbox user | `POST /api/v1/cashout/sandbox/users` | `{ "reference_id": "test-player-1", "email": "test@yourcompany.com" }` | | Set user identity verification status | `POST /api/v1/principal-accounts/{zbd_user_id}/kyc-status` | `{ "status": "approved", "country_code": "US" }` | | Add sandbox balance | `POST /api/v1/monetary-accounts/{monetary_account_id}/fund` | `{ "amount": 5000 }` | | Remove sandbox balance | `POST /api/v1/monetary-accounts/{monetary_account_id}/deplete` | `{ "amount": 5000 }` | The identity verification `status` must be `approved`, `rejected`, or `processing`. Include `country_code` when setting `approved`. Balance helper amounts use currency minor units. For USD, `5000` means `$50.00`. Disclosure acceptance can be tested in sandbox with the same flow as production. If you need to verify that cashout is blocked by a missing disclosure, make sure your sandbox setup is not using a bypass mode that skips the disclosure gate. ACH return-code simulation and explicit payout failure-mode controls are still being wired for sandbox. Until those controls are available, use the webhook examples to prepare your handler for `cashout.failed` and `cashout.returned` events. ## Sandbox Webhooks Sandbox webhooks fire to your `webhook_url` with the same payload structure as production: ```json theme={null} { "event_id": "evt_sandbox_123", "event_type": "cashout.initiated", "user_reference_id": "test-player-1", "amount_cents": 5000, "status": "initiated", "occurred_at": "2026-06-01T12:00:00Z" } ``` In sandbox, the `cashout.completed` webhook can fire shortly after `cashout.initiated` since there's no real ACH settlement delay. See [Server Webhooks](/widget/webhooks) for the full event reference. ## Moving to Production When you're ready to go live: Verify all flows work: funding, cashout, webhooks, error handling Set your production webhook endpoint in the dashboard Replace your sandbox API key with the production key Contact ZBD to fund your production CRB pool Deploy and monitor your first real cashouts Production cashouts move real money. Make sure your webhook handler correctly processes `cashout.completed`, `cashout.failed`, and `cashout.returned` events before going live. # Submit Disclosure Acceptance Source: https://docs.zbdpay.com/widget/submit-disclosure-acceptance POST https://api.zbdpay.com/api/v1/widget/users/{userId}/disclosures Record disclosure acceptance for a widget user. ## Description Records the user's acceptance of the latest version of each disclosure type in `acceptedDisclosureTypeIds`. Use this from your server after your application has presented the applicable documents and collected the user's acceptance. Do not call this endpoint from a browser, mobile client, game client, or WebView. ## Configuration ### Header Parameters Your ZBD project API key. Content Type ### Path Parameters The ZBD user ID returned when the widget user is created or resolved. ### Body Parameters Disclosure type IDs accepted by the user. ZBD records acceptance for the latest version of each type. ```bash cURL theme={null} curl -X POST https://api.zbdpay.com/api/v1/widget/users/{userId}/disclosures \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "acceptedDisclosureTypeIds": [6] }' ``` ```json 201 theme={null} { "success": true, "message": "Publisher user disclosure agreements recorded successfully.", "data": { "acceptedDisclosureTypeIds": [6] }, "error": null } ``` ## Disclosure Type IDs | Disclosure type | Type ID | | ------------------------- | ------: | | Terms of Service | `2` | | Privacy Policy | `3` | | Electronic Funds Transfer | `6` | ## Existing Users If a user already exists and needs to accept a current disclosure, call this endpoint. Do not rely on an idempotent user-create call to update disclosure acceptance for an existing user. ## Errors | Status | Reason | | ------ | ------------------------------------------------------------------------------------- | | `400` | `acceptedDisclosureTypeIds` is empty, missing, or contains an unknown disclosure type | | `401` | Missing or invalid API key | | `403` | The API key is not allowed to access widget disclosure APIs | | `404` | `userId` does not belong to the project associated with the API key | # Update User Source: https://docs.zbdpay.com/widget/update-user PATCH https://api.zbdpay.com/api/v1/widget/users/{user_id} Update a widget user's details. ## Description Updates a widget user's details. This is a partial update. Only the fields you send are changed, and today `email` is the only updatable field. Use this when a user changes the email tied to their account. The new email becomes the recipient for OTP verification codes in the widget, so any code requested after the update is sent to the new address. ## Configuration ### Header Parameters Your ZBD project API key. Content Type ### Path Parameters The ZBD user ID returned by Create User. ### Body Parameters The user's new email address. Used for OTP verification in the widget. ```bash cURL theme={null} curl -X PATCH https://api.zbdpay.com/api/v1/widget/users/4ac4fd8a-cc2c-4d03-af09-a76f4e89d652 \ -H "apikey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "email": "new-email@example.com" }' ``` ```json 200 theme={null} { "success": true, "message": "Cashout user updated successfully.", "data": { "id": "4ac4fd8a-cc2c-4d03-af09-a76f4e89d652", "reference_id": "player-42", "email": "new-email@example.com" }, "error": null } ``` # Server Webhooks Source: https://docs.zbdpay.com/widget/webhooks Reference for ZBD Widget publisher webhook events, delivery, signing, retries, and payloads. ## Overview ZBD sends webhook events to your configured HTTPS endpoint when a widget cashout or reversal changes status. Your endpoint validates the signature, acknowledges receipt, and processes the event asynchronously. Webhook configuration is completed with ZBD during publisher onboarding. ZBD provides the shared signing secret used to sign each webhook request. The MVP does not support subscribing to individual event types. ZBD sends all widget webhook event types to the configured endpoint, and your system should filter by the `event_type` field. ## Webhook Delivery Request ZBD sends each event as an HTTP POST request: ```http theme={null} POST X-ZBD-Signature: X-ZBD-Event-Id: X-ZBD-Event-Type: Content-Type: application/json ``` Your endpoint should return HTTP `200` within 10 seconds to acknowledge receipt. Any non-200 response, timeout, 5xx response, or connection failure triggers retry. Validate the webhook signature against the raw request body before processing the event. ## Webhook Event Types | Event type | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `cashout.initiated` | The user initiated a cashout against the publisher-funded balance, and ZBD submitted the ACH to the partner bank. | | `cashout.completed` | The ACH settled successfully and funds landed in the user's external bank account. | | `cashout.failed` | The ACH was rejected before settlement, such as a closed account, invalid routing, or NSF response. | | `cashout.returned` | The ACH was reversed after settlement within the return window. | | `reversal.status_changed` | A reversal request previously submitted by the publisher changed status. | ## Cashout Webhook Events Cashout lifecycle webhooks identify the event, reference the publisher's user identifier, include the current status, include the amount, and include the time the event occurred at the bank or inside ZBD. ### Cashout Initiated ```json theme={null} { "event_id": "evt_a1b2c3", "event_type": "cashout.initiated", "user_reference_id": "player-42", "amount_cents": 500, "status": "initiated", "occurred_at": "2026-07-15T17:45:00Z" } ``` ### Cashout Completed ```json theme={null} { "event_id": "evt_d4e5f6", "event_type": "cashout.completed", "user_reference_id": "player-42", "amount_cents": 500, "status": "completed", "occurred_at": "2026-07-17T14:20:00Z" } ``` ### Cashout Failed ```json theme={null} { "event_id": "evt_g7h8i9", "event_type": "cashout.failed", "user_reference_id": "player-42", "amount_cents": 500, "status": "failed", "reason_code": "R02", "reason_description": "account_closed", "occurred_at": "2026-07-16T09:12:00Z" } ``` ### Cashout Returned ```json theme={null} { "event_id": "evt_j1k2l3", "event_type": "cashout.returned", "user_reference_id": "player-42", "amount_cents": 500, "status": "returned", "reason_code": "R10", "reason_description": "unauthorized_by_customer", "occurred_at": "2026-07-25T11:30:00Z" } ``` ### Cashout Payload Fields | Field | Type | Description | | -------------------- | ------- | ----------------------------------------------------------------------------------------- | | `event_id` | string | Unique event identifier. Use this value for deduplication. | | `event_type` | string | One of `cashout.initiated`, `cashout.completed`, `cashout.failed`, or `cashout.returned`. | | `user_reference_id` | string | Your stable user identifier from Create User. | | `amount_cents` | integer | Cashout amount in USD cents. | | `status` | string | Current cashout status. | | `reason_code` | string | Bank reason code. Present on failed and returned events. | | `reason_description` | string | Human-readable bank reason. Present on failed and returned events. | | `occurred_at` | string | ISO 8601 timestamp for when the event occurred. | ## Reversal Webhook Events Reversal status webhooks identify the reversal action, the new status, the reversal method, the amount, and the timestamp. ### Reversal Completed ```json theme={null} { "event_id": "evt_m4n5o6", "event_type": "reversal.status_changed", "reversal_id": "R-002", "amount_cents": 500, "status": "completed", "method": "bank_reversal", "occurred_at": "2026-07-20T15:45:00Z" } ``` ### Reversal Failed ```json theme={null} { "event_id": "evt_p7q8r9", "event_type": "reversal.status_changed", "reversal_id": "R-002", "amount_cents": 500, "status": "failed", "method": "bank_reversal", "occurred_at": "2026-07-20T15:45:00Z" } ``` ### Reversal Payload Fields | Field | Type | Description | | -------------- | ------- | ------------------------------------------------------------------ | | `event_id` | string | Unique event identifier. Use this value for deduplication. | | `event_type` | string | Always `reversal.status_changed`. | | `reversal_id` | string | Unique reversal identifier. | | `amount_cents` | integer | Reversal amount in USD cents. | | `status` | string | New reversal status, such as `completed` or `failed`. | | `method` | string | Reversal method, such as `bank_reversal` for bank-level reversals. | | `occurred_at` | string | ISO 8601 timestamp for when the status changed. | ## Webhook Event Sources `cashout.initiated` events are generated when the user submits a cashout through the ZBD Widget and ZBD submits the ACH to the partner bank. `cashout.completed`, `cashout.failed`, and `cashout.returned` events are generated from partner bank webhook callbacks. ZBD translates the bank-level status update into the publisher-facing event. `reversal.status_changed` events are generated when a reversal transitions between states. Instant reversals emit on completion. Queued bank-level reversals emit when ZBD records the final outcome from the partner bank. ## Retries ZBD attempts initial delivery within seconds of event generation. If your endpoint does not return HTTP `200`, ZBD retries with exponential backoff: roughly 1 minute, 5 minutes, 30 minutes, 2 hours, then 12 hours between attempts. After approximately five failed delivery attempts over roughly 15 hours, the event moves to a dead-letter queue and ZBD operations is alerted to investigate. ## Idempotency Webhook delivery is at least once. The same event may be delivered more than once if a retry happens. Store and deduplicate by `event_id`. ## Auditability ZBD logs each webhook delivery attempt, including the attempt timestamp, response or timeout reason, and final outcome.