Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions widget.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: "Embed a hosted cashout experience so your users can withdraw earne

## Overview

The ZBD Widget is an embeddable iframe that handles the full cashout flow for your users — identity verification (KYC), bank account linking (Plaid), and ACH payouts. Your backend handles user creation, session minting, and balance funding; the widget handles everything else.
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

Expand All @@ -28,19 +28,52 @@ To get sandbox running quickly, start with the [sandbox guide](/widget/sandbox):
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. Deplete User POST /api/v1/widget/users/deplete (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 <iframe src="{widget_url}" /> (your frontend)
7. Listen for Events window.addEventListener("message", ...) (your frontend)
```

Use `POST /api/v1/widget/users/deplete` when your server needs to debit points back from a widget user's point balance.
Use `POST /api/v1/widget/users/deplete` to reverse an available balance that
your server previously funded, such as when a reward is canceled.

## Events

Expand Down
11 changes: 9 additions & 2 deletions widget/create-session.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@ api: "POST https://api.zbdpay.com/api/v1/widget/users/session"

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, KYC (Onfido), bank linking (Plaid), and cashout initiation internally.
The widget handles OTP email verification, identity verification (Onfido), bank linking (Plaid), and cashout initiation internally.

The widget also checks whether the user has any outstanding session disclosures. Cashout-specific disclosures are enforced when a cashout is submitted. See [Disclosure Agreements](/widget/disclosures).
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

Expand Down
2 changes: 1 addition & 1 deletion widget/create-user.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Provisions a user in ZBD for your project. Idempotent — if a user with the sam
Call this from your server before creating a widget session.

<Note>
Some cashout methods require the user to accept the latest disclosure before cashout can proceed. The hosted widget can collect this during the session. If your integration records disclosure acceptance before opening the widget, see [Disclosure Agreements](/widget/disclosures).
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).
</Note>

## Configuration
Expand Down
6 changes: 5 additions & 1 deletion widget/deplete-user.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ api: "POST https://api.zbdpay.com/api/v1/widget/users/deplete"

## Description

Debit publisher points from a widget user's point balance and return them to the project's configured sink/source account. Amount is in **currency minor units**. For USDZ, `100` equals `$1.00` worth of points.
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.

Expand Down
42 changes: 27 additions & 15 deletions widget/disclosures.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,30 @@ description: "Understand how widget user disclosure acceptance works before cash

## Overview

Some widget 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.
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.

The widget session exposes outstanding session disclosures, and the cashout backend separately verifies cashout-specific disclosures before a payout is submitted.
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 creates a widget session.
3. Your frontend opens the returned widget_url.
4. The widget loads the user's session status, including outstanding_disclosures.
5. If a session disclosure is outstanding, the widget asks the user to accept it.
6. ZBD records the acceptance against the latest disclosure version.
7. The user can continue to bank linking, KYC, and cashout.
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.
```

<Info>
If your integration only embeds the hosted widget, the disclosure prompt and acceptance flow are handled inside the widget. Your backend still creates users, funds balances, creates sessions, and processes webhooks as usual.
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.
</Info>

## Outstanding Disclosures on User Creation
Expand All @@ -31,7 +37,7 @@ You do not need a separate call to discover what a user still owes. The [Create

`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 (`is_current`, `accepted_at`), so you can show accepted vs. outstanding.
- [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.

<Note>
Expand All @@ -54,9 +60,13 @@ The widget disclosure endpoints can check and record acceptance for all current

## Session Status

During a widget session, ZBD returns outstanding session disclosure information to the widget runtime. The field is named `outstanding_disclosures`.
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.

Today this session response includes Terms of Service and Privacy Policy disclosure checks. Cashout-specific checks, such as Electronic Funds Transfer acceptance for ACH, are enforced when the user submits the cashout.
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:

Expand Down Expand Up @@ -88,11 +98,13 @@ Example session status shape:
}
```

If `outstanding_disclosures` is empty, the user is current on all session disclosures returned by this endpoint.
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 Outside the Widget
## Recording acceptance before the widget

Most integrations should let the hosted widget collect the user's acceptance. If your integration collects disclosure acceptance before the widget opens, use the widget disclosure APIs from your server. The API key determines the project context, so these endpoints only require the widget user ID in the path.
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.

<Warning>
Disclosure endpoints require your server-side API key. Do not call these endpoints directly from a browser, mobile client, game client, or WebView.
Expand Down
11 changes: 11 additions & 0 deletions widget/fund-user.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ Credit publisher points to a widget user's point balance. Amount is in **currenc

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
Expand Down
2 changes: 1 addition & 1 deletion widget/get-disclosure-status.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ api: "GET https://api.zbdpay.com/api/v1/widget/users/{userId}/disclosures"

Returns the user's acceptance status for each current disclosure type.

Use this from your server only if your integration needs to check disclosure acceptance outside the hosted widget. Most integrations can let the widget handle disclosure prompts during the session.
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.

Expand Down
2 changes: 1 addition & 1 deletion widget/get-user.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ api: "GET https://api.zbdpay.com/api/v1/widget/users/{user_id}"

## Description

Returns the user's details including their current identity verification status. Use this to check if a user has completed KYC before showing cashout options.
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

Expand Down
12 changes: 6 additions & 6 deletions widget/sandbox.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "Sandbox"
description: "Test the full ZBD Widget flow without real money, real KYC, or real bank transfers."
description: "Test the full ZBD Widget flow without real money, real identity verification, or real bank transfers."
---

## Overview
Expand All @@ -19,7 +19,7 @@ Every ZBD project includes a **sandbox environment** for testing the ZBD Widget
|---|---|---|
| **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 |
| **KYC / Identity** | Controlled through sandbox KYC status helpers; no live Onfido review | Required for bank cashouts |
| **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 |
Expand Down Expand Up @@ -49,7 +49,7 @@ curl -X POST https://sandbox-api.zbdpay.com/api/v1/cashout/sandbox/users \
```

<Note>
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 KYC status. Use `POST /api/v1/widget/users` only for production widget users.
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.
</Note>

### 3. Fund the Test User
Expand Down Expand Up @@ -91,7 +91,7 @@ Embed the returned `widget_url` in an iframe. In sandbox, the URL should point a
</Note>

<Note>
When you reach KYC in sandbox, use a **US address** so the approval path can complete successfully.
When you reach identity verification in sandbox, use a **US address** so the approval path can complete successfully.
</Note>

## Sandbox Controls
Expand All @@ -103,11 +103,11 @@ For user rewards, use `POST /api/v1/widget/users/fund` and `POST /api/v1/widget/
| Control | Endpoint | Request body |
|---|---|---|
| Create sandbox user | `POST /api/v1/cashout/sandbox/users` | `{ "reference_id": "test-player-1", "email": "test@yourcompany.com" }` |
| Set user KYC status | `POST /api/v1/principal-accounts/{zbd_user_id}/kyc-status` | `{ "status": "approved", "country_code": "US" }` |
| 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 }` |

KYC `status` must be `approved`, `rejected`, or `processing`. Include `country_code` when setting `approved`.
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`.

Expand Down
4 changes: 3 additions & 1 deletion widget/submit-disclosure-acceptance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ api: "POST https://api.zbdpay.com/api/v1/widget/users/{userId}/disclosures"

Records the user's acceptance of the latest version of each disclosure type in `acceptedDisclosureTypeIds`.

Use this from your server only if your integration collects disclosure acceptance outside the hosted widget. Do not call this endpoint from a browser, mobile client, game client, or WebView.
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

Expand Down