# Introduction

Welcome to the Bead developer documentation.

Use these docs to:

* accept payments
* submit merchant onboarding applications
* manage approved merchants, locations, and terminals
* retrieve reporting and settlement data

**What you'll find here**

| Area                                                | What you can do                                                                                                           |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| [Payments](/payments)                               | Create hosted payment pages, run server-to-server payments, track payment status, and consume payment webhooks            |
| [Onboarding](/onboarding)                           | Submit merchant applications by API, send a hosted application to a signer, and track application status through approval |
| [Entity Management](/entity-management)             | Create and manage merchants, locations, terminals, permissions, and related entities after approval                       |
| [Settlement](/settlement) & [Reporting](/reporting) | Retrieve payment history, batches, settlements, and deposits for reconciliation                                           |
| [Reference Guide](/reference-guide)                 | Review shared concepts, schemas, endpoint index, environments, and error behavior                                         |
| [Changelog](/changelog)                             | Track developer-facing documentation and API updates                                                                      |

**Before you begin**

Make sure you have the right credentials for the API families you plan to use.

**Sandbox credentials**

* **Payments**
  * API key
  * `merchantId`
  * `terminalId`
* **Onboarding**
  * onboarding API key
* **Other Bead APIs**
  * API key, unless the specific endpoint's documentation states otherwise

Also confirm:

* your environment base URLs
* whether you are working in Sandbox or Production
* your preferred HTTP client or SDK workflow

If you are missing credentials, contact your Bead partner contact.

**Pick a path**

**Accept a payment**

Start with:

* Quick Start
* Payments

**Submit a merchant application**

Start with:

* Onboarding

**Manage entities after approval**

Start with:

* Entity Management

**Reconcile transactions and funding**

Start with:

* Reporting
* Settlement

**Authentication at a glance**

Bead supports multiple authentication methods depending on the API family. Across current API families, an API key sent in the `X-Api-Key` header is the preferred method.

**Payments API**

Payments requests use an API key.

Send:

```http
X-Api-Key: {apiKey}
```

**Onboarding API**

Onboarding requests use an API key.

Send:

```http
X-Api-Key: {apiKey}
```

When onboarding is initiated by API, Bead emails the merchant signer a secure link to complete the hosted onboarding flow.

For onboarding create requests:

* full application requests include the signer inside `merchantData.stakeholders` using `isApplicationSigner: true`, alongside the rest of the merchant data
* short application requests use `signerFirstName`, `signerLastName`, and `signerEmail` as top-level fields
* onboarding create requests require `cryptoEnvironment`

**Reporting, Settlement, and Entity Management APIs**

These API families also use an API key sent in the same header.

Send:

```http
X-Api-Key: {apiKey}
```

Some endpoints within these families may still support OAuth 2.0 with OpenID Connect for existing integrations that were provisioned that way. If an endpoint's own documentation specifies OAuth, follow that guidance for that specific endpoint. If it doesn't, use `X-Api-Key`.

Send (legacy, only where specifically required):

```http
Authorization: Bearer {access_token}
```

**Legacy Payments support**

Existing Payments integrations may still use OAuth password grant.

New Payments integrations should use:

```http
X-Api-Key: {apiKey}
```

**Guidance**

* Do not send deprecated onboarding `apiKey` fields in request bodies
* Use the request shapes documented on the current onboarding pages
* For onboarding create requests, set `cryptoEnvironment` to the environment you are targeting
* If you are starting a new integration and were not given OAuth client credentials, use `X-Api-Key` and you can ignore the OAuth flow entirely

See **Authentication** for detailed examples.

**Environments**

Bead supports separate environments for development and live traffic.

**Sandbox**

Use Sandbox for:

* development
* integration testing
* end-to-end onboarding testing
* webhook validation

**Production**

Use Production for:

* live merchant traffic
* live settlement activity
* certified integrations

See **Sandbox and production URLs** for current base URLs, token URLs, and onboarding environment rules.

**Important onboarding note**

Onboarding create requests require `cryptoEnvironment`.

Use:

* `sandbox` in Sandbox
* `production` in Production

This applies to both onboarding create flows:

* `POST /merchant-onboarding/applications`
* `POST /merchant-onboarding/applications-short`

**API conventions**

Bead APIs generally follow these conventions:

* JSON request and response bodies
* ISO 8601 timestamps in UTC
* standardized error responses for validation and authorization failures
* idempotent handling where supported by the endpoint
* environment-specific credentials and identifiers

**Webhooks and events**

Bead supports webhook-driven integrations for event-based workflows.

Typical uses include:

* payment status updates
* onboarding application lifecycle events

If you use webhooks:

* respond quickly with `2xx`
* process events idempotently
* log event identifiers for troubleshooting
* treat your webhook consumer like a production service

**Production readiness checklist**

Before going live, confirm the following.

**Authentication**

* you can authenticate successfully for each API family you use
* your Sandbox and Production credentials are stored separately
* your code applies the correct auth header for each API family

**Payments**

* you can create a payment
* you can retrieve payment status successfully
* your system handles payment lifecycle outcomes correctly

**Onboarding**

* you can submit an application successfully
* you can track application status through approval
* your integration sends the correct onboarding request shape
* your integration sets `cryptoEnvironment` correctly for the target environment

**Webhooks**

* webhook endpoints are reachable
* webhook events are logged and processed safely
* retries do not create duplicate side effects

**Observability**

* request identifiers and event identifiers are logged
* errors are traceable by environment and API family
* support-ready context is captured for failed calls

**Resilience**

* retry behavior is safe
* validation errors are surfaced clearly
* environment mismatches are easy to diagnose

**Support**

Use your Bead partner contact for:

* credentials
* environment questions
* onboarding workflow support
* certification and production-readiness questions

When opening support requests, include relevant identifiers such as:

* `trackingId`
* `paymentPageId`
* `merchantId`
* `terminalId`
* `applicationId`

Include the approximate timestamp of the failing request and the environment used.


# Quick Start

## Quick Start

This guide walks you through creating a crypto payment, launching the hosted payment page, and confirming the final outcome. It also includes the required behavior for underpayments and overpayments.

> **This quick start uses crypto, which runs on live blockchain networks and requires real assets.** If you only need to confirm your integration end to end — API keys, terminal configuration, hosted page, and webhooks — the faster path is a Klarna test, which needs no real assets. See [Choosing a Test Method](/testing) and [Test with Klarna](/testing/test-with-klarna-recommended).

**What you will build**

* Create a hosted crypto payment session
* Present the hosted payment page to a customer
* Confirm payment completion using webhooks or status polling
* Handle `underpaid` and `overpaid` outcomes correctly
* Understand the reclaim flow for any unconverted crypto

**Prerequisites**

You will need:

* A sandbox terminal API key, which is the secret `apiKey`, not a masked value
* A sandbox `merchantId` and `terminalId`
* A server or tool capable of making HTTPS requests, such as curl, Postman, or your backend
* A compatible crypto wallet funded with the real asset and real network fee token required for the tender you are testing

Optional but recommended:

* A webhook endpoint for payment status updates

Bead Sandbox crypto payments use live blockchain networks. Test payments require real assets and real network fee tokens. Keep test amounts small.

For USDC on Base and USDC on Solana, the minimum Bead payment amount is $1.00 USD. This minimum is separate from any live-network fee the payer's wallet may require to submit the transaction, such as ETH on Base or SOL on Solana.

**Environment and base URLs**

Common sandbox base URL:

* `https://api.test.devs.beadpay.io`

Payments endpoints used in this quick start:

* `POST /Payments/crypto`
* `GET /Payments/{id}/tracking`

Authentication used in this quick start:

* `X-Api-Key: {apiKey}`

Sandbox crypto payments use live blockchain networks. Sandbox changes the Bead API environment, merchant configuration, credentials, and hosted payment environment. It does not mean crypto payments are sent over blockchain testnets.

**Step 1: Create a payment**

Create a hosted payment session and receive a `paymentId` plus one or more hosted `paymentUrls`.

For USDC on Base and USDC on Solana, create payments with `requestedAmount` of `1.00` or higher.

**Request**

```http
POST https://api.test.devs.beadpay.io/Payments/crypto
```

Headers:

```http
X-Api-Key: {apiKey}
Content-Type: application/json
Accept: application/json
```

Minimal body:

```json
{
"terminalId": "TERM-123",
"merchantId": "MERCH-456",
"requestedAmount": 100.00
}
```

For USDC on Base and USDC on Solana, the smallest valid Bead payment request is:

```json
{
"terminalId": "TERM-123",
"merchantId": "MERCH-456",
"requestedAmount": 1.00
}
```

Recommended additions:

* `refundEmail`: If you have an email address for the payer, include it. This is the email Bead uses to send reclaim instructions when reclaim is required.
* `redirectUrl`: If you want the hosted experience to return the shopper to your confirmation page.
* `webhookUrls`: If you want webhooks for this payment in addition to any terminal level webhook configuration.

Example expanded body:

```json
{
"terminalId": "TERM-123",
"merchantId": "MERCH-456",
"requestedAmount": 100.00,
"reference": "ORDER-0001",
"description": "Sample order",
"refundEmail": "customer@example.com",
"redirectUrl": "https://merchant.example.com/payment-return",
"webhookUrls": [
"https://merchant.example.com/webhooks/payments"
]
}
```

**Example curl**

```bash
curl -s -X POST "https://api.test.devs.beadpay.io/Payments/crypto" \
-H "X-Api-Key: {apiKey}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"terminalId": "TERM-123",
"merchantId": "MERCH-456",
"requestedAmount": 100.00,
"reference": "ORDER-0001",
"refundEmail": "customer@example.com"
}'
```

Example USDC test payment curl:

```bash
curl -s -X POST "https://api.test.devs.beadpay.io/Payments/crypto" \
-H "X-Api-Key: {apiKey}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"terminalId": "TERM-123",
"merchantId": "MERCH-456",
"requestedAmount": 1.00,
"reference": "ORDER-0001",
"refundEmail": "customer@example.com",
"tenderTypes": ["usdcBase"]
}'
```

**Response**

You will receive a response similar to:

```json
{
"paymentId": "pay_c10b29e3c8104e0f8dc139c20d9eeb6c",
"trackingId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
"paymentUrls": [
"https://pay.qa.beadpay.io/crypto?paymentPageId=a12b34c56d789e01"
]
}
```

Save:

* `paymentId`: your primary lookup key for status checks, webhooks, reporting, and support
* `paymentUrls[0]`: the hosted payment page URL to present to the customer

**Step 2: Present the hosted payment page**

Open the hosted page URL from `paymentUrls`.

Common options:

* Web: open in a new tab or window, or embed if allowed in your flow
* Mobile: open in an in-app browser or webview
* POS or terminal assisted: display the hosted page on a customer facing device

What the customer will do in a typical crypto wallet flow:

1. Select an asset, such as BTC or USDC
2. View a QR code or payment details
3. Open their wallet app and scan the QR code
4. Manually enter the crypto amount in their wallet app
5. Submit the transfer

Important: because the wallet requires the payer to enter an amount manually, miskeyed amounts can result in `underpaid` or `overpaid`.

The payer's wallet must hold the correct asset on the correct live network. For example, USDC on Base requires USDC on Base for the payment amount and ETH on Base for network fees. USDC on Solana requires USDC on Solana for the payment amount and SOL for network fees.

**Step 3: Confirm the final result**

You should always confirm the final status before fulfilling an order. Use either webhooks or status polling.

**Option A: Webhooks, recommended**

If you configured webhooks, either terminal level or per payment `webhookUrls`, your server will receive events when the payment status changes.

Recommended handling:

* Verify the webhook signature if your configuration supports it
* Persist the event quickly
* Return `200 OK` promptly
* Treat the webhook as the primary status signal

**Option B: Polling**

Use the tracking endpoint with the `paymentId` until the payment reaches a terminal status.

Request:

```http
GET https://api.test.devs.beadpay.io/Payments/{paymentId}/tracking
```

Example curl:

```bash
curl -s -X GET "https://api.test.devs.beadpay.io/Payments/{paymentId}/tracking" \
-H "X-Api-Key: {apiKey}" \
-H "Accept: application/json"
```

Recommended polling behavior:

* Use a reasonable interval, such as 2 seconds, during an active checkout
* Stop polling once a terminal status is returned
* Prefer webhooks for production scale

**Step 4: Handle status outcomes**

The payment status is returned in `statusCode`.

**Non terminal statuses**

| statusCode   | Meaning                                      |
| ------------ | -------------------------------------------- |
| `created`    | Payment created, waiting for customer action |
| `processing` | Funds detected and processing is in progress |

**Terminal statuses**

| statusCode  | Meaning                                                | What you should do                              |
| ----------- | ------------------------------------------------------ | ----------------------------------------------- |
| `completed` | Customer paid the requested amount                     | Fulfill and mark as paid                        |
| `underpaid` | Customer sent less than requested                      | Treat as not successful payment, do not fulfill |
| `overpaid`  | Customer sent more than requested                      | Treat as not successful payment, do not fulfill |
| `expired`   | Payment window ended without a valid completion        | Treat as not successful payment, do not fulfill |
| `invalid`   | Irregular condition, funds not eligible for conversion | Treat as not successful payment, do not fulfill |
| `cancelled` | Payment cancelled                                      | Treat as not successful payment, do not fulfill |

**Underpayments and overpayments**

Underpayments and overpayments occur because the payer manually enters the crypto amount in their wallet app.

**Underpaid**

Definition: the payer sent less than the requested amount.

Integrator behavior:

* Treat as not successful payment
* Do not fulfill the order
* If the customer still wants to pay, create a new payment and start a new hosted checkout session

What Bead does:

* The crypto received remains unconverted
* The full amount received is returned to the payer through the reclaim process

**Overpaid**

Definition: the payer sent more than the requested amount.

Integrator behavior:

* Treat as not successful payment
* Do not fulfill the order
* If the customer still wants to pay, create a new payment and start a new hosted checkout session

What Bead does:

* The crypto received remains unconverted
* The full amount received is returned to the payer through the reclaim process

**Reclaiming unconverted crypto**

Reclaim is how Bead returns any unconverted crypto to the payer.

Reclaim can occur when the payment is:

* `underpaid`
* `overpaid`
* `expired`
* `invalid`
* `cancelled`

**Email behavior for reclaim**

* If you provided `refundEmail` in the Create Payment request, Bead emails reclaim instructions to that address when reclaim is required.
* If `refundEmail` was not provided, the hosted payment page prompts the payer to enter an email address in underpaid and overpaid outcomes so Bead can send reclaim instructions.

Recommended best practice:

* Provide `refundEmail` whenever you have it, especially for digital and virtual checkout flows.

**Troubleshooting**

**401 Unauthorized**

Common causes:

* Missing `X-Api-Key` header
* Using a masked value instead of the real `apiKey`
* Using a key from the wrong environment

**Hosted payment page does not load**

Common causes:

* Using an old or incorrect `paymentUrls` value
* Terminal is not configured for the tender types you are testing
* Environment mismatch between API base URL and hosted page environment

**Payment creation returns a validation error**

Common causes:

* For USDC on Base or USDC on Solana, `requestedAmount` is less than `1.00`
* The tender type in `tenderTypes` is not enabled for the merchant, location, or terminal
* Required fields for the configured terminal or tender flow are missing

**Wallet cannot send the payment**

Common causes:

* The wallet has the payment asset but not the required live-network fee token
* The wallet asset is on the wrong network, such as USDC on Solana instead of USDC on Base
* The wallet cannot scan the QR code or send to the address or invoice shown on the hosted payment page

**Status never reaches a terminal state**

Common causes:

* Customer did not complete the wallet transfer
* Customer sent funds on the wrong network
* Customer sent funds to an old or recently used address instead of the current hosted payment page address
* Webhook endpoint is failing and you are relying only on webhooks without polling

**Payment reached `expired` before the customer paid**

The quote window timed out. The expired payment URL cannot be reused. Direct the customer to a new payment session. If they sent funds after the session expired, Bead's reclaim process will return them.

**Next steps**

After you complete this quick start in sandbox:

* Review [Payment Statuses](/payments/payment-statuses) for full status definitions and guidance
* Review [Under and Over Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling) for detailed integrator workflows
* Review [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto) to understand reclaim timing and payer experience
* Review [Test Crypto Payments](/testing/test-crypto-payments) for live-network testing instructions and wallet funding guidance
* Implement production grade webhooks and internal state mapping before going live


# Testing

Choose the right way to validate your integration in Sandbox.

## Testing

Bead supports several ways to validate your integration in Sandbox. This section helps you choose the right test method and links to a step by step guide for each one.

### Choosing a Test Method

For most integrators, **Klarna is the recommended default**. It exercises the full end to end payment flow, create payment, hosted checkout, completion, and webhook or status confirmation, without requiring any real assets. Klarna provides sandbox test credentials, so you can prove your integration works before touching a live wallet or crypto network.

Pick your test method based on your terminal type and what you need to prove.

| Test method                       | Terminal type        | Real assets required?                                              | Best for                                                                      |
| --------------------------------- | -------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| **Klarna** (recommended)          | Virtual and physical | No. Uses Klarna sandbox test credentials                           | The default way to prove the full payment flow works, on either terminal type |
| **Venmo**                         | Virtual only         | No. Sandbox does not draw from a live wallet                       | Validating the wallet redirect and QR flow on a virtual terminal              |
| **PayPal**                        | Virtual only         | No. Uses a sandbox PayPal test account                             | Validating the wallet approval flow on a virtual terminal                     |
| **Crypto** (USDC, BTC, Lightning) | Virtual and physical | Yes. Live blockchain networks, real assets plus network fee tokens | Only when you specifically need to validate a crypto tender                   |

#### Start here

If you only need to confirm that your API keys, terminal configuration, hosted payment page, and webhooks all work together, run a **Klarna test** first. It is the fastest path to a completed payment and needs no funded wallet.

If you are integrating on a **virtual terminal** and want to validate wallet based tenders, you can also test **Venmo** and **PayPal**. Both complete in Sandbox without drawing from a live account.

Reach for **crypto testing** only when a crypto tender is in scope for your integration. Sandbox crypto payments run on **live blockchain networks and require real assets** plus a network fee token, so keep test amounts small.

#### Guides in this section

* **Test with Klarna (recommended)** — full sandbox walkthrough for virtual and physical terminals
* **Test with Venmo** — virtual terminal wallet flow
* **Test with PayPal** — virtual terminal wallet flow
* **Test Crypto Payments** — live network testing, wallet funding, and fee tokens
* **Run a Test Payment in the API Reference** — fire a test payment from your browser

#### Related

* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)
* [Testing Settlement in the Test Environment](/settlement/testing-settlement-in-the-test-environment)
* [Sandbox and production URLs](/faqs-and-troubleshooting/environment-and-testing/sandbox-and-production-urls)


# Test with Klarna (recommended)

Klarna is the recommended default way to test your Bead integration. It runs the full end to end payment flow, create payment, hosted checkout, completion, and webhook or status confirmation, without requiring any real assets. Klarna provides sandbox test credentials, so you can prove your integration works before touching a live wallet or crypto network. It works on both virtual and physical terminals.

For a comparison of all test methods, see [Choosing a Test Method](/testing).

This guide shows how to create a payment request that launches Bead's hosted checkout page, select Klarna in the UI, complete the Sandbox checkout using Klarna test credentials, and verify the result.

### 1 – Prerequisites

| Item                                                  | Notes                                                                                  |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------- |
| Klarna enabled for your Sandbox merchant and terminal | Contact developer support if Klarna is not yet activated for your Sandbox environment. |
| Sandbox base URL                                      | `https://api.test.devs.beadpay.io`                                                     |
| Hosted payment page base                              | Returned in `paymentUrls`                                                              |
| Terminal API key                                      | You need the real API key value. The masked API key is not usable.                     |
| `merchantId` and `terminalId`                         | Must match the terminal API key you were issued.                                       |
| Webhook endpoint (recommended)                        | Publicly reachable and registered in the Bead dashboard.                               |

Authentication note: Payments endpoints use header based authentication. Send your API key as `X-Api-Key` on every request.

### 2 – Create Payment Request

Send a `POST /Payments/crypto` to generate the hosted checkout URL.

Required headers:

* `X-Api-Key: {apiKey}`
* `Content-Type: application/json`

Klarna testing requires customer details in the request. Minimum required fields depend on the terminal `type`:

| Terminal `type` | Required fields                                                              |
| --------------- | ---------------------------------------------------------------------------- |
| `virtual`       | `merchantId`, `terminalId`, `requestedAmount`, `refundEmail`, and `customer` |
| `physical`      | `merchantId`, `terminalId`, `requestedAmount`, and `customer`                |

The `customer` object should include:

* `firstName`
* `lastName`
* `email`
* `address`
* `address2`
* `city`
* `state`
* `postalCode`
* `countryCode`

**Sandbox region note:** In Sandbox, Klarna credentials are US region scoped. Always use a US customer address (`countryCode: US`). A non US customer object will be rejected with a "Could Not Create Payment" error. This is a sandbox credential limitation, not a product limitation.

**Example request for terminal `type` = `virtual`**

```bash
curl -s -X POST "https://api.test.devs.beadpay.io/Payments/crypto" \
-H "Content-Type: application/json" \
-H "X-Api-Key: {apiKey}" \
-d '{
"merchantId": "{merchantId}",
"terminalId": "{terminalId}",
"requestedAmount": 1,
"refundEmail": "alex.tester@example.com",
"customer": {
"firstName": "Jordan",
"lastName": "Reed",
"email": "jordan.reed@example.com",
"address": "456 Market St",
"address2": "Suite 210",
"city": "Chicago",
"state": "IL",
"postalCode": "60601",
"countryCode": "US"
}
}'
```

**Example request for terminal `type` = `physical`**

```bash
curl -s -X POST "https://api.test.devs.beadpay.io/Payments/crypto" \
-H "Content-Type: application/json" \
-H "X-Api-Key: {apiKey}" \
-d '{
"merchantId": "{merchantId}",
"terminalId": "{terminalId}",
"requestedAmount": 1,
"customer": {
"firstName": "Taylor",
"lastName": "Brooks",
"email": "taylor.brooks@example.com",
"address": "88 Lakeview Ave",
"address2": "",
"city": "Denver",
"state": "CO",
"postalCode": "80202",
"countryCode": "US"
}
}'
```

Example response:

```json
{
"paymentId": "pay_c10b29e3c8104e0f8dc139c20d9eeb6c",
"trackingId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
"paymentUrls": [
"https://pay.qa.beadpay.io/crypto?paymentPageId=a12b34c56d789e01"
]
}
```

Save these values:

* `paymentUrls[0]` for the hosted checkout URL
* `paymentId` your primary lookup key for status checks, webhooks, reporting, and support

### 3 – Launch Hosted Payment Page

Use `paymentUrls[0]` to launch checkout.

| Option   | Details                                                 |
| -------- | ------------------------------------------------------- |
| Redirect | Redirect the shopper's browser to the hosted page URL.  |
| Embed    | Load the hosted page URL in an iframe inside your site. |

The shopper now sees Bead's hosted payment page.

### 4 – Select Klarna and Open Klarna Checkout

1. In the Bead hosted UI, select Klarna as the payment method.
2. Bead displays a Klarna QR code linked to Klarna's Sandbox checkout.

How the tester can open Klarna checkout:

* Scan the QR code with a phone camera to open Klarna in the mobile browser.
* Click or tap the QR code to copy the Klarna checkout URL, then paste it into any browser.

### 5 – Complete Klarna Sandbox Checkout

Use the following test values in Klarna's Sandbox flow.

| Field             | Test value                            |
| ----------------- | ------------------------------------- |
| Phone number      | `716-220-4669`                        |
| PIN (if prompted) | `123456`                              |
| Card number       | `4111 1111 1111 1111`                 |
| Card expiry       | Any future MM/YY, for example `12/29` |
| Card CVV          | `123`                                 |

Follow Klarna's prompts until the shopper is returned to your `redirectUrl`, if you provided one.

### 6 – Verify Result

You can verify completion using webhooks and, optionally, an API status check.

**Option A: Webhook verification**

Confirm your webhook endpoint receives a payment event indicating completion. Look for a status field that indicates the payment is completed.

**Option B: API status check (optional)**

Use the same terminal API key approach as `POST /Payments/crypto`.

Endpoint: `GET /Payments/{paymentId}/tracking`

Example curl:

```bash
curl -s -X GET "https://api.test.devs.beadpay.io/Payments/{paymentId}/tracking" \
-H "X-Api-Key: {apiKey}" \
-H "Accept: application/json"
```

What to confirm:

* The response shows the payment in a completed state.
* Amount fields reflect a successful payment for the requested amount.

### Troubleshooting

Common issues:

* **Klarna does not appear as a tender option.** Klarna may not be enabled for the Sandbox merchant or terminal. Confirm enablement with developer support.
* **`401 Unauthorized`.** The API key is missing or invalid, or the header name is wrong. The header must be exactly `X-Api-Key`.
* **`403 Forbidden`.** The API key is valid but not permitted for the `merchantId` or `terminalId` in your request. Confirm the IDs match the credential set you were issued and that you are using the correct environment.
* **Validation error on payment creation.** Confirm you are using the correct minimum request shape for the terminal `type`. Terminals with `type` = `virtual` require `refundEmail`; terminals with `type` = `physical` do not. Both terminal types require the `customer` object for Klarna testing.
* **Redirect does not happen.** Confirm you set a valid `redirectUrl`. If not provided, Bead will display a hosted confirmation screen instead of redirecting.

### 7 – Next Steps

* Test different amounts and run cancellation or expiry scenarios.
* Validate your production readiness using webhooks plus status checks and avoid tight polling loops.
* On a virtual terminal, you can also validate wallet tenders with [Test with Venmo](/reference-guide/enumerations-and-schemas/tender-types/alternative-payments) and [Test with PayPal](/reference-guide/enumerations-and-schemas/tender-types/alternative-payments).

### Related

* [Choosing a Test Method](/testing)
* [Which consumers can use Klarna, PayPal, and Venmo?](/faqs-and-troubleshooting/payments-faqs/which-consumers-can-use-klarna-paypal-and-venmo)
* [Run a Test Payment in the API Reference](/payments/run-a-test-payment-in-the-api-reference)


# Test with Venmo

Venmo is available for testing on a terminal with `type` = `virtual`. In Sandbox, Venmo does not draw funds from a live Venmo account, so you can complete a test payment with any test account details and confirm the wallet redirect and completion flow end to end. No real assets are required.

Use this guide when you want to validate the Venmo wallet flow. For the recommended default end to end test, see [Test with Klarna](/testing/test-with-klarna-recommended). For a comparison of all test methods, see [Choosing a Test Method](/testing).

### 1 – Prerequisites

| Item                                                 | Notes                                                                                 |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Venmo enabled for your Sandbox merchant and terminal | Contact developer support if Venmo is not yet activated for your Sandbox environment. |
| Terminal with `type` = `virtual`                     | Venmo testing applies to terminals with `type` = `virtual`, not `physical`.           |
| Sandbox base URL                                     | `https://api.test.devs.beadpay.io`                                                    |
| Hosted payment page base                             | Returned in `paymentUrls`                                                             |
| Terminal API key                                     | You need the real API key value. The masked API key is not usable.                    |
| `merchantId` and `terminalId`                        | Must match the terminal API key you were issued.                                      |
| Webhook endpoint (recommended)                       | Publicly reachable and registered in the Bead dashboard.                              |

Authentication note: Payments endpoints use header based authentication. Send your API key as `X-Api-Key` on every request.

### 2 – Create Payment Request

Send a `POST /Payments/crypto` to generate the hosted checkout URL. On a terminal with `type` = `virtual`, include `refundEmail` and a `customer` object.

Required headers:

* `X-Api-Key: {apiKey}`
* `Content-Type: application/json`

To present Venmo as the tender, include `venmo` in `tenderTypes`.

**Example request**

```bash
curl -s -X POST "https://api.test.devs.beadpay.io/Payments/crypto" \
-H "Content-Type: application/json" \
-H "X-Api-Key: {apiKey}" \
-d '{
"merchantId": "{merchantId}",
"terminalId": "{terminalId}",
"requestedAmount": 1,
"refundEmail": "alex.tester@example.com",
"tenderTypes": ["venmo"],
"customer": {
"firstName": "Jordan",
"lastName": "Reed",
"email": "jordan.reed@example.com",
"address": "456 Market St",
"address2": "Suite 210",
"city": "Chicago",
"state": "IL",
"postalCode": "60601",
"countryCode": "US"
}
}'
```

Example response:

```json
{
"paymentId": "pay_c10b29e3c8104e0f8dc139c20d9eeb6c",
"trackingId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
"paymentUrls": [
"https://pay.qa.beadpay.io/crypto?paymentPageId=a12b34c56d789e01"
]
}
```

Save these values:

* `paymentUrls[0]` for the hosted checkout URL
* `paymentId` your primary lookup key for status checks, webhooks, reporting, and support

### 3 – Launch Hosted Payment Page

Use `paymentUrls[0]` to launch checkout.

| Option   | Details                                                 |
| -------- | ------------------------------------------------------- |
| Redirect | Redirect the shopper's browser to the hosted page URL.  |
| Embed    | Load the hosted page URL in an iframe inside your site. |

The shopper now sees Bead's hosted payment page.

### 4 – Select Venmo and Open Venmo Checkout

1. In the Bead hosted UI, select Venmo as the payment method.
2. Bead presents the Venmo flow. Venmo redirects to the Venmo app, with a QR code fallback available.

How the tester can open Venmo checkout:

* On mobile, follow the redirect into the Venmo app.
* On desktop, scan the QR code with a phone, or use the QR fallback to open the Venmo flow.

### 5 – Complete Venmo Sandbox Checkout

Complete the Venmo flow using test account details. Because Sandbox does not draw funds from a live Venmo account, you can register and use any test account to complete the payment. No real balance is used and no live wallet is charged.

> Confirm the exact Sandbox test account values with developer support if your environment requires a specific test account. In most Sandbox configurations, any well formed test account details will complete the flow.

Follow the Venmo prompts until the shopper is returned to your `redirectUrl`, if you provided one.

### 6 – Verify Result

**Option A: Webhook verification**

Confirm your webhook endpoint receives a payment event indicating completion. Look for a status field that indicates the payment is completed.

**Option B: API status check (optional)**

Endpoint: `GET /Payments/{paymentId}/tracking`

```bash
curl -s -X GET "https://api.test.devs.beadpay.io/Payments/{paymentId}/tracking" \
-H "X-Api-Key: {apiKey}" \
-H "Accept: application/json"
```

What to confirm:

* The response shows the payment in a completed state.
* Amount fields reflect a successful payment for the requested amount.

### Production eligibility

Sandbox does not enforce Venmo consumer eligibility, but production does. In production, Venmo requires a consumer who is:

* Physically located in the United States
* Using a US based phone number
* 18 or older
* On Venmo app version 7.38.2 or newer

See [Which consumers can use Klarna, PayPal, and Venmo?](/faqs-and-troubleshooting/payments-faqs/which-consumers-can-use-klarna-paypal-and-venmo) for full details.

### Troubleshooting

* **Venmo does not appear as a tender option.** Venmo may not be enabled for the Sandbox merchant or terminal, or the terminal is not `type` = `virtual`. Confirm enablement and terminal type with developer support.
* **`401 Unauthorized`.** The API key is missing or invalid, or the header name is wrong. The header must be exactly `X-Api-Key`.
* **`403 Forbidden`.** The API key is valid but not permitted for the `merchantId` or `terminalId` in your request. Confirm the IDs match the credential set you were issued and that you are using the correct environment.
* **Validation error on payment creation.** On a terminal with `type` = `virtual`, confirm you included `refundEmail` and a complete `customer` object.
* **Redirect does not happen.** Confirm you set a valid `redirectUrl`. If not provided, Bead will display a hosted confirmation screen instead of redirecting.

### Next Steps

* Test different amounts and run cancellation or expiry scenarios.
* On a terminal with `type` = `virtual`, you can also validate [Test with PayPal](/reference-guide/enumerations-and-schemas/tender-types/alternative-payments).
* Validate your production readiness using webhooks plus status checks and avoid tight polling loops.

### Related

* [Choosing a Test Method](/testing)
* [Alternative Payments](/reference-guide/enumerations-and-schemas/tender-types/alternative-payments)
* [Which consumers can use Klarna, PayPal, and Venmo?](/faqs-and-troubleshooting/payments-faqs/which-consumers-can-use-klarna-paypal-and-venmo)


# Test with PayPal

PayPal is available for testing on a terminal with `type` = `virtual`. In Sandbox, PayPal uses a sandbox test account, so you can complete a test payment and confirm the wallet approval and completion flow end to end. No real assets are required and no live PayPal balance is used.

Use this guide when you want to validate the PayPal wallet flow. For the recommended default end to end test, see [Test with Klarna](/testing/test-with-klarna-recommended). For a comparison of all test methods, see [Choosing a Test Method](/testing).

### 1 – Prerequisites

| Item                                                  | Notes                                                                                  |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------- |
| PayPal enabled for your Sandbox merchant and terminal | Contact developer support if PayPal is not yet activated for your Sandbox environment. |
| Terminal with `type` = `virtual`                      | PayPal testing applies to terminals with `type` = `virtual`, not `physical`.           |
| Sandbox base URL                                      | `https://api.test.devs.beadpay.io`                                                     |
| Hosted payment page base                              | Returned in `paymentUrls`                                                              |
| Terminal API key                                      | You need the real API key value. The masked API key is not usable.                     |
| `merchantId` and `terminalId`                         | Must match the terminal API key you were issued.                                       |
| Sandbox PayPal test account                           | Provided below. Used to log in and approve the payment in the PayPal Sandbox.          |
| Webhook endpoint (recommended)                        | Publicly reachable and registered in the Bead dashboard.                               |

Authentication note: Payments endpoints use header based authentication. Send your API key as `X-Api-Key` on every request.

### 2 – Create Payment Request

Send a `POST /Payments/crypto` to generate the hosted checkout URL. On a terminal with `type` = `virtual`, include `refundEmail` and a `customer` object.

Required headers:

* `X-Api-Key: {apiKey}`
* `Content-Type: application/json`

To present PayPal as the tender, include `paypal` in `tenderTypes`.

**Example request**

```bash
curl -s -X POST "https://api.test.devs.beadpay.io/Payments/crypto" \
-H "Content-Type: application/json" \
-H "X-Api-Key: {apiKey}" \
-d '{
"merchantId": "{merchantId}",
"terminalId": "{terminalId}",
"requestedAmount": 1,
"refundEmail": "alex.tester@example.com",
"tenderTypes": ["paypal"],
"customer": {
"firstName": "Jordan",
"lastName": "Reed",
"email": "jordan.reed@example.com",
"address": "456 Market St",
"address2": "Suite 210",
"city": "Chicago",
"state": "IL",
"postalCode": "60601",
"countryCode": "US"
}
}'
```

Example response:

```json
{
"paymentId": "pay_c10b29e3c8104e0f8dc139c20d9eeb6c",
"trackingId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
"paymentUrls": [
"https://pay.qa.beadpay.io/crypto?paymentPageId=a12b34c56d789e01"
]
}
```

Save these values:

* `paymentUrls[0]` for the hosted checkout URL
* `paymentId` your primary lookup key for status checks, webhooks, reporting, and support

### 3 – Launch Hosted Payment Page

Use `paymentUrls[0]` to launch checkout.

| Option   | Details                                                 |
| -------- | ------------------------------------------------------- |
| Redirect | Redirect the shopper's browser to the hosted page URL.  |
| Embed    | Load the hosted page URL in an iframe inside your site. |

The shopper now sees Bead's hosted payment page.

### 4 – Select PayPal and Open PayPal Checkout

1. In the Bead hosted UI, select PayPal as the payment method.
2. Bead opens the PayPal approval flow. The customer approves the payment via the PayPal app or web.

### 5 – Complete PayPal Sandbox Checkout

Log in to the PayPal Sandbox flow using the shared test account below and approve the payment. Because this is a sandbox account, no real PayPal balance is used.

| Field            | Test value          |
| ---------------- | ------------------- |
| Email / username | <test+2@beadpay.io> |
| Password         | Password1           |

> If the test account stops working, contact developer support to have the Sandbox credentials rotated.

Follow the PayPal prompts until the shopper is returned to your `redirectUrl`, if you provided one.

### 6 – Verify Result

**Option A: Webhook verification**

Confirm your webhook endpoint receives a payment event indicating completion. Look for a status field that indicates the payment is completed.

**Option B: API status check (optional)**

Endpoint: `GET /Payments/{paymentId}/tracking`

```bash
curl -s -X GET "https://api.test.devs.beadpay.io/Payments/{paymentId}/tracking" \
-H "X-Api-Key: {apiKey}" \
-H "Accept: application/json"
```

What to confirm:

* The response shows the payment in a completed state.
* Amount fields reflect a successful payment for the requested amount.

### Production eligibility

PayPal has no US residency restriction and supports customers globally, including all US states and territories. The consumer needs an active PayPal account. See [Which consumers can use Klarna, PayPal, and Venmo?](/faqs-and-troubleshooting/payments-faqs/which-consumers-can-use-klarna-paypal-and-venmo) for full details.

### Troubleshooting

* **PayPal does not appear as a tender option.** PayPal may not be enabled for the Sandbox merchant or terminal, or the terminal is not `type` = `virtual`. Confirm enablement and terminal type with developer support.
* **`401 Unauthorized`.** The API key is missing or invalid, or the header name is wrong. The header must be exactly `X-Api-Key`.
* **`403 Forbidden`.** The API key is valid but not permitted for the `merchantId` or `terminalId` in your request. Confirm the IDs match the credential set you were issued and that you are using the correct environment.
* **Validation error on payment creation.** On a terminal with `type` = `virtual`, confirm you included `refundEmail` and a complete `customer` object.
* **Cannot log in to the PayPal Sandbox account.** Confirm you are using the current Sandbox test account values above. If they have changed, contact developer support.
* **Redirect does not happen.** Confirm you set a valid `redirectUrl`. If not provided, Bead will display a hosted confirmation screen instead of redirecting.

### Next Steps

* Test different amounts and run cancellation or expiry scenarios.
* On a terminal with `type` = `virtual`, you can also validate [Test with Venmo](/testing/test-with-venmo).
* Validate your production readiness using webhooks plus status checks and avoid tight polling loops.

### Related

* [Choosing a Test Method](/testing)
* [Alternative Payments](/reference-guide/enumerations-and-schemas/tender-types/alternative-payments)
* [How are PayPal clawbacks reflected in settlement?](/faqs-and-troubleshooting/settlement-faqs/how-are-paypal-clawbacks-reflected-in-settlement)


# Test Crypto Payments

Crypto testing validates a crypto tender end to end: create payment, hosted checkout, on-chain transfer, and completion. Unlike Klarna, Venmo, and PayPal, Sandbox crypto payments run on **live blockchain networks and require real assets** plus a network fee token. Use this method only when a crypto tender is in scope for your integration.

For the recommended default test that needs no real assets, see [Test with Klarna](/testing/test-with-klarna-recommended). For a comparison of all test methods, see [Choosing a Test Method](/testing).

### Sandbox crypto uses live networks

Bead Sandbox changes the API environment, merchant configuration, credentials, and hosted payment environment. It does **not** move crypto to blockchain testnets. Test payments settle on live networks with real assets.

* Test payments require real assets and real network fee tokens.
* Keep test amounts small.
* For USDC on Base and USDC on Solana, the minimum Bead payment amount is $1.00 USD. This is separate from any network fee the payer's wallet needs to submit the transaction.

### 1 – Prerequisites

| Item                           | Notes                                                                                                     |
| ------------------------------ | --------------------------------------------------------------------------------------------------------- |
| Sandbox base URL               | `https://api.test.devs.beadpay.io`                                                                        |
| Terminal API key               | You need the real API key value. The masked API key is not usable.                                        |
| `merchantId` and `terminalId`  | Must match the terminal API key you were issued.                                                          |
| Funded wallet                  | A compatible wallet holding the real asset and the real network fee token for the tender you are testing. |
| Webhook endpoint (recommended) | Publicly reachable and registered in the Bead dashboard.                                                  |

Authentication note: Payments endpoints use header based authentication. Send your API key as `X-Api-Key` on every request.

### 2 – Choose a test tender

For most initial tests, **USDC on Base** is recommended: low fees, fast confirmation, and wide wallet support. **USDC on Solana** is also a good choice, especially with Phantom, because Solana fees are typically very low. Once you have validated the hosted flow with USDC, you can extend testing to Bitcoin and Bitcoin Lightning if those tenders are enabled for your project.

Fee assets required per tender:

| Payment token and network | Fee asset                 | Notes                                                                                                       |
| ------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------- |
| USDC on Base              | ETH on Base               | Minimum Bead payment amount is $1.00 USD.                                                                   |
| USDC on Solana            | SOL on Solana             | Minimum Bead payment amount is $1.00 USD. Wallet may need a small SOL balance as a network minimum.         |
| Bitcoin on chain          | BTC                       | Fees are deducted in BTC per transaction and vary with congestion. Keep extra BTC above the invoice amount. |
| Bitcoin Lightning         | BTC in Lightning channels | Fees are handled by the Lightning network and wallet routing. Fund channels before testing.                 |

Acquire only what you need for functional testing. As a rough guide, a few cents of the fee asset per transaction is often enough under typical conditions. Always review the fee estimate shown in your wallet before sending.

### 3 – Create Payment Request

Send a `POST /Payments/crypto` to generate the hosted checkout URL. For USDC on Base or USDC on Solana, use `requestedAmount` of `1.00` or higher.

Required headers:

* `X-Api-Key: {apiKey}`
* `Content-Type: application/json`

To target a specific crypto tender, include it in `tenderTypes` (for example, `usdcBase`).

**Example request**

```bash
curl -s -X POST "https://api.test.devs.beadpay.io/Payments/crypto" \
-H "X-Api-Key: {apiKey}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"merchantId": "{merchantId}",
"terminalId": "{terminalId}",
"requestedAmount": 1.00,
"reference": "ORDER-0001",
"refundEmail": "customer@example.com",
"tenderTypes": ["usdcBase"]
}'
```

Example response:

```json
{
"paymentId": "pay_c10b29e3c8104e0f8dc139c20d9eeb6c",
"trackingId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
"paymentUrls": [
"https://pay.qa.beadpay.io/crypto?paymentPageId=a12b34c56d789e01"
]
}
```

Save these values:

* `paymentId` your primary lookup key for status checks, webhooks, reporting, and support
* `paymentUrls[0]` the hosted checkout URL to present to the customer

Providing `refundEmail` is recommended. If a payment is underpaid, overpaid, expired, or invalid, the crypto remains unconverted and is returned to the payer through the reclaim process, and Bead emails reclaim instructions to that address.

### 4 – Present the Hosted Payment Page

Open `paymentUrls[0]` for the payer. In a typical crypto wallet flow the payer:

1. Selects an asset, such as USDC or BTC.
2. Views a QR code or payment details.
3. Opens their wallet app and scans the QR code.
4. Manually enters the crypto amount in their wallet app.
5. Submits the transfer.

Because the wallet requires the payer to enter the amount manually, miskeyed amounts can result in `underpaid` or `overpaid`. The payer's wallet must hold the correct asset on the correct live network, plus the fee token (for example, USDC on Base for the amount and ETH on Base for fees).

### 5 – Verify Result

**Option A: Webhook verification**

Confirm your webhook endpoint receives a payment event indicating completion.

**Option B: API status check (optional)**

Endpoint: `GET /Payments/{paymentId}/tracking`

```bash
curl -s -X GET "https://api.test.devs.beadpay.io/Payments/{paymentId}/tracking" \
-H "X-Api-Key: {apiKey}" \
-H "Accept: application/json"
```

The payment status is returned in `statusCode`. Fulfill only when it reaches `completed`. Treat `underpaid`, `overpaid`, `expired`, `invalid`, and `cancelled` as not successful.

### Common testing reminders

* Sandbox crypto payments use live networks, not testnets.
* Test payments require real assets and real network fee tokens. Keep amounts small.
* For USDC on Base and USDC on Solana, use `requestedAmount` of `1.00` or higher.
* Make sure the asset and network in the wallet match the asset and network shown on the hosted payment page.
* Do not send funds to a recently used address from your wallet history. Always scan or copy the address shown on the current Bead hosted payment page.

### Related

* [Choosing a Test Method](/testing)
* [Crypto Testing and Fees](/faqs-and-troubleshooting/payments-faqs/crypto-testing-and-fees)
* [Compatible Crypto Wallets](/reference-guide/operational-guides/compatible-crypto-wallets)
* [Under and Over Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling)
* [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto)


# Authentication

Bead supports multiple authentication methods depending on the API family you are calling.

For current integrations:

* **Payments API** uses an API key sent in the `X-Api-Key` header
* **Onboarding API** uses an API key sent in the `X-Api-Key` header
* **Other Bead APIs** may use OAuth 2.0 Bearer tokens depending on the product area and endpoint family
* **Legacy Payments integrations** may still use OAuth 2.0 password grant with the `bead-terminal` client

If you are unsure which authentication method applies to your use case, ask your Bead contact.

### When to use each authentication method

* **Payments API**: use an API key in the `X-Api-Key` header for new integrations
* **Onboarding API**: use an API key in the `X-Api-Key` header
* **Other Bead APIs**: follow the authentication requirements documented for that API family or endpoint
* **Legacy Payments integrations**: OAuth 2.0 password grant remains supported where already in use

### Payments authentication (preferred): API key

A Payments API key is the secret credential used to authenticate Payments requests. You must send the full API key value on each request in the `X-Api-Key` header.

The masked API key is a display value only and cannot be used to authenticate.

#### Required request header

```http
X-Api-Key: {apiKey}
```

For JSON requests, also send:

```http
Content-Type: application/json
```

#### Example curl (Payments)

```bash
curl -s -X POST "{BASE_URL}/Payments/crypto" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: {apiKey}" \
  -d '{
    "merchantId": "{merchantId}",
    "terminalId": "{terminalId}",
    "requestedAmount": 25.00,
    "reference": "ORDER123"
  }'
```

#### Payments API key notes

* Treat the API key like a password
* Store it in a secret manager or environment variable
* Do not log the full value
* If you believe an API key is exposed, rotate it immediately through your Bead contact

#### Payments troubleshooting

* `401 Unauthorized`: the API key is missing, invalid, or sent using the wrong header name. The header must be exactly `X-Api-Key`.
* If you try OAuth flows such as `client_credentials` for `POST /Payments/crypto`, you may see errors like `Public client not allowed to retrieve service account`. Payments authentication for that endpoint uses `X-Api-Key`.

### Onboarding authentication: API key

Onboarding requests use an API key sent in the `X-Api-Key` header.

#### Required request header

```http
X-Api-Key: {apiKey}
```

For JSON requests, also send:

```http
Content-Type: application/json
Accept: application/json
```

#### Current onboarding request guidance

Use the API key header model for onboarding requests.

Do not send deprecated `apiKey` fields in onboarding request bodies.

For the current onboarding request model:

* **Full application** requests use `signer` and `merchantData`
* **Minimal application** requests use `signerName` and `signerEmail`
* Onboarding create requests require `cryptoEnvironment`

#### Current onboarding create endpoints

```http
POST /merchant-onboarding/applications
POST /merchant-onboarding/applications-short
```

#### Onboarding environment note

For onboarding create requests, set `cryptoEnvironment` to match the environment you are targeting:

* `sandbox` in Sandbox
* `production` in Production

#### Transition note

Legacy bearer-token authentication may still work in some environments during migration, but new integrations should use API key authentication consistently across the Onboarding APIs.

### Payments authentication (legacy): OAuth password grant (`bead-terminal`)

Existing integrators may continue to authenticate Payments using OAuth 2.0 password grant with a terminal username and password. New Payments integrations should use the API key method above.

#### Password grant request fields

* `grant_type=password`
* `client_id=bead-terminal`
* `username`
* `password`

#### Example curl (legacy Payments)

```bash
curl -s -X POST "{identity_base_url}/realms/{realm}/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "client_id=bead-terminal" \
  -d "username={username}" \
  -d "password={password}"
```

### OAuth token endpoint (OpenID Connect)

OAuth access tokens are used by Bead APIs and endpoints that require OAuth authentication. The same token endpoint format and realms are also used for legacy Payments integrations.

#### Endpoint format

```http
POST {identity_base_url}/realms/{realm}/protocol/openid-connect/token
```

#### Required header

```http
Content-Type: application/x-www-form-urlencoded
```

#### Placeholders

* `identity_base_url`: typically `https://identity.beadpay.io`
* `realm`: environment realm
  * `nonprod` for Sandbox and other non-production environments
  * `prod` for Production

#### Common token URL examples

```
https://identity.beadpay.io/realms/nonprod/protocol/openid-connect/token
https://identity.beadpay.io/realms/prod/protocol/openid-connect/token
```

### Typical token response (OAuth)

A successful token request returns a JSON object that includes fields such as:

* `access_token`
* `expires_in`
* `refresh_token`
* `refresh_expires_in`
* `token_type` set to `Bearer`

Store tokens securely and avoid logging full token values in plain text.

### Refresh token request (OAuth)

Use the refresh token grant to obtain a new access token without prompting for credentials again.

#### Fields

* `grant_type=refresh_token`
* `client_id={client_id}`
* `refresh_token={refresh_token}`

#### Example curl

```bash
curl -s -X POST "{identity_base_url}/realms/{realm}/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "client_id={client_id}" \
  -d "refresh_token={refresh_token}"
```

### Using an OAuth access token

Add the access token as an `Authorization` header on every request to APIs that require OAuth authentication.

```http
Authorization: Bearer {access_token}
```

### Optional scopes (OAuth)

Scopes are not required for core API calls.

* Include `scope=openid` only if you need an ID token or plan to call a user info endpoint
* Include `profile` or `email` only if you consume those claims in a portal or user-facing application
* If you do not need identity claims, omit the `scope` parameter and treat the token as a pure API access token

### Quick troubleshooting summary

#### API key requests

* Confirm the header name is exactly `X-Api-Key`
* Confirm you are using the full API key value
* Confirm the key matches the correct environment
* Do not send masked keys or deprecated body fields

#### Onboarding requests

* Use API key auth
* Use the current request shapes, not `signerInfo`
* Set `cryptoEnvironment` correctly
* Keep environment and credentials aligned

#### OAuth requests

* Verify realm selection: `nonprod` vs `prod`
* Verify client and grant type
* Verify access token freshness
* Send the token as `Authorization: Bearer {access_token}`


# Payments

Bead provides a unified, secure, and streamlined solution for accepting crypto and digital wallet payments through a hosted payment page. The Payments API is designed to minimize PCI and wallet complexity by keeping tender selection and checkout on Bead's hosted experience while still giving your application full control over when a payment is created and how you confirm completion. By default, Bead handles hosted tender selection based on the tenders enabled for the terminal. For advanced integrations that want a more tailored shopper journey, payment creation can also optionally preselect a specific tender or constrain the hosted experience to a specific tender subset. This optional behavior is documented on [Create Payment](/payments/create-payment).

Payments work together with Reporting and Settlement:

* Payments create and track individual transactions
* Reporting provides historical payment views for partners, merchants, and terminals
* Settlement provides batch-level settlement views for reconciliation

#### Start here

Most integrators start with these pages:

* [Quick Start](/quick-start)
* [Create Payment](/payments/create-payment)
* [Payment Statuses](/payments/payment-statuses)
* [Receipts](/payments/receipts)
* [Payment Webhooks](/payments/payment-webhooks) and [Webhook Event Reference](/reference-guide/operational-guides/webhook-event-reference)

#### Authentication

Payments use header-based authentication with a terminal API key.

**Preferred for new integrations**

* Send `X-Api-Key: {apiKey}` on every Payments request
* The API key is the secret credential
* The masked API key is not usable

**Legacy support**

* Existing integrators may still use OAuth password grant and `Authorization: Bearer {access_token}`
* New Payments integrations should use the API key method

See Authentication for full details and examples.

#### Key features

Hosted payment page

A secure hosted checkout experience that reduces integration complexity and keeps tender flows off your infrastructure. By default, the hosted page manages tender selection for the shopper. Advanced integrations can optionally preselect a tender or tender subset at payment creation time when they want a more tailored entry point.

Real-time webhooks

Status change notifications delivered to your backend so you can react as payments move through their lifecycle.

Payment status API

A simple GET endpoint to retrieve the latest status for a single payment using its `paymentId`.

#### Core workflows

**Create a hosted payment URL**

Use the Payments API to create a payment and receive the hosted checkout URL.

* Endpoint: `POST /Payments/crypto`
* Response: `paymentId`, `trackingId`, `paymentUrls`

Go to Create Payment for request fields, examples, terminal `type` guidance, tender preselection behavior, and best practices.

**Present the hosted page**

Use `paymentUrls[0]` to launch checkout:

* Web checkout: open in a new tab or embed in an iframe
* In-person or demo flows: display the hosted page and have the customer scan the QR code

If you do not send optional tender preselection fields, the hosted page uses the terminal's normal eligible tender-selection experience. If you do send them, the hosted page can take the shopper directly into a dedicated tender flow or a curated tender subset.

**Confirm completion**

You should confirm the final payment result before fulfilling an order.

You have two options:

* Webhooks, recommended for production
* Polling `GET /Payments/{paymentId}/tracking` for ad hoc checks and support workflows

Go to Payment Statuses for status values and examples.

#### API capabilities

Hosted payment

Generate hosted URLs for customers to complete their payments. The hosted page handles tender selection, crypto and wallet flows, and customer prompts. Create Payment also documents the optional tender preselection behavior available for advanced integrations.

Payment status

Check payment status using a simple API call by `paymentId`. Use this for ad hoc checks and troubleshooting.

Webhooks

Configure real-time status updates delivered to your backend so you do not need to poll the status endpoint.

#### Environments

Sandbox

* Base URL: `https://api.test.devs.beadpay.io`
* Sandbox uses Bead's test API environment, test merchant configuration, test credentials, and hosted test payment experience.
* Bead Sandbox crypto payments use live blockchain networks. Crypto test payments require real assets and real network fee tokens. Keep test amounts small.
* For tender-specific testing guidance, including supported wallets, fee tokens, and minimum payment amounts, see [Test Crypto Transactions](/payments/test-crypto-transactions) and [Crypto Testing and Fees](/faqs-and-troubleshooting/payments-faqs/crypto-testing-and-fees).

Production

* Base URL: `https://api.devs.beadpay.io`

See Sandbox and production URLs for environment switching guidance.

#### Security and reliability best practices

* Keep the API key server-side. Create payments from your backend whenever possible. Do not embed the API key in browser or mobile apps.
* Treat the API key like a password. Store it in a secret manager or environment variable and avoid logging it.
* Use webhooks for scale. Webhooks reduce polling load and provide faster reaction time.
* Always verify final status. Even if the browser redirects or the hosted UI shows success, confirm status via webhook or tracking endpoint.

#### Common errors

401 Unauthorized

API key missing, invalid, or wrong header name. The header must be exactly `X-Api-Key`.

403 Forbidden

Request authenticated, but the API key is not permitted for the merchant or terminal context, or the request is hitting the wrong environment.

See Resolving 403 Forbidden When Creating Payments for a detailed checklist.

#### Next steps

* Create your first payment in Sandbox using [Quick Start](/quick-start)
* Add webhooks and validate event handling using the [Webhook Event Reference](/reference-guide/operational-guides/webhook-event-reference)
* Implement a status check flow using [Payment Statuses](/payments/payment-statuses)
* Use [Reporting](/reporting) and [Settlement](/settlement) to build reconciliation and operational dashboards


# Create Payment

Create a payment with `POST /Payments/crypto` to generate one or more hosted payment URLs. The hosted payment page handles the checkout experience for supported crypto and digital wallet tenders.

You can optionally pass tender-selection details when you want to direct the shopper into a specific tender flow or limit the hosted page to a curated tender subset. If you omit `tenderTypes`, Bead uses the active tender configuration for the terminal.

#### Overview

Endpoint:

`POST /Payments/crypto`

Sandbox base URL:

`https://api.test.devs.beadpay.io`

Use this endpoint to:

* create a hosted payment session
* return one or more hosted payment URLs
* optionally pass order, invoice, customer, receipt, redirect, webhook, and tender-selection details
* allow Bead's hosted payment page to manage the payer experience

For an end-to-end Sandbox flow, see [Quick Start](/quick-start).

#### Step 1: Authenticate

Payments use header-based authentication with a terminal API key.

The terminal API key is the secret credential used to authenticate requests. The masked API key is a display value only and cannot be used to authenticate.

Required headers:

```http
X-Api-Key: {apiKey}
Content-Type: application/json
Accept: application/json
```

Authentication guidance:

* Preferred for new integrations: use `X-Api-Key`
* Supported for existing legacy Payments integrations: OAuth password grant with `Authorization: Bearer {access_token}`
* Do not place the API key in the request body or URL
* Do not expose the API key in browser or mobile client code

See Authentication for full token and legacy OAuth details.

#### Step 2: Create a hosted payment URL

Send a request to create a payment. The response includes `paymentId`, `trackingId`, and `paymentUrls`.

Endpoint:

```http
POST https://api.test.devs.beadpay.io/Payments/crypto
```

Headers:

```http
X-Api-Key: {apiKey}
Content-Type: application/json
Accept: application/json
```

#### Request shape guidance

At a minimum, payment creation requests should include:

* `terminalId`
* `merchantId`
* `requestedAmount`

Other common fields include:

* `reference`
* `description`
* `cartItems`
* `redirectUrl`
* `emailReceipt`
* `smsReceipt`
* `refundEmail`
* `webhookUrls`
* `customer`
* `tenderTypes`

Required fields can vary by tender type, terminal configuration, and risk or compliance requirements. Use the simplest request body that matches your flow, and add optional fields only when you need them.

#### Terminal type considerations

Terminals have a `type` of either `virtual` or `physical`.

A virtual terminal is generally used for ecommerce, hosted checkout, remote payment, or other customer-not-present flows.

A physical terminal is generally used for in-store, point-of-sale, or customer-present flows.

The terminal type helps determine which tender types and payment experiences are available, but it does not mean the `customer` object is always required.

**Terminal type = virtual**

For virtual terminals, the `customer` object is optional unless the selected tender, merchant configuration, risk review, or compliance flow specifically requires customer details.

For common hosted checkout flows, you can create a payment without a `customer` object.

Use `refundEmail` when you already have the payer's email and want Bead to send reclaim instructions directly if reclaim is required. If `refundEmail` is not provided, the hosted payment page can prompt the payer for an email address when reclaim is needed.

**Terminal type = physical**

For physical terminals, the request can also use the basic payment fields unless the selected tender or terminal configuration requires additional information.

Physical terminal flows may be customer-present and may not always need `customer` details. If your tender flow does require customer details, include the complete `customer` object.

**Customer object rule**

Do not send a partial `customer` object.

If you include `customer`, provide all required customer fields:

* `firstName`
* `lastName`
* `email`
* `address`
* `city`
* `state`
* `postalCode`
* `countryCode`

Optional customer fields include:

* `phone`
* `address2`
* `birthday`

If you send a partial customer object, the API may return `400 Bad Request` with validation errors.

#### Cart items

Use `cartItems` when you want to pass order, invoice, or basket-level detail with the payment.

`cartItems` can be omitted when you do not have item-level detail. Do not send placeholder or blank cart item values.

When you include `cartItems`, each item should include:

* `id` — your item, SKU, invoice line, or service identifier
* `name` — optional display name for the item; may be `null`
* `quantity` — quantity for the item
* `price` — price for the item

When possible, keep the cart item detail consistent with `requestedAmount` so reporting and reconciliation remain clear.

#### Tender type selection

Use `tenderTypes` when you want to limit or direct a payment to one or more specific tender types that are already available for the merchant, location, and terminal.

Passing `tenderTypes` on a payment request does not enable a new tender type for the merchant or terminal. It only narrows or directs the hosted payment experience to tender types that are already approved and active for that payment context.

If `tenderTypes` is omitted, Bead uses the available tender configuration for the terminal and hosted payment experience.

Before using payment-level `tenderTypes`, confirm the terminal's active tender set with `GET /Terminals/{terminalId}`. If a tender type is not active on the terminal, the merchant may need additional onboarding, pricing, provider enrollment, or operational support before that tender can be used.

**BTC (on-chain) tender selection**

BTC (on-chain) refers to Bitcoin on-chain payments. When you want to direct the hosted payment experience to BTC (on-chain), use `bitcoin` in the `tenderTypes` array.

Do not use `bitcoin_Lightning` for BTC (on-chain). Bitcoin Lightning is a separate BTC payment rail with a faster customer-facing payment experience.

Only present BTC (on-chain) when the merchant's payment environment can support delayed confirmation. BTC (on-chain) is usually appropriate for invoices, ecommerce orders with later shipment, service payments before pickup, deposits, and high-ticket or high-touch purchases. It is usually not appropriate for fast in-person checkout or instant digital delivery.

If a payment is limited to BTC (on-chain), make sure your integration can:

* show the customer that the payment has been detected when the status reaches `processing`
* keep the order, invoice, or service request in a pending payment state
* use webhooks or status polling to detect the final status
* fulfill only after the payment reaches `completed`

#### Minimum payment amounts

Minimum payment amounts can vary by tender.

For USDC on Base and USDC on Solana, the minimum Bead payment amount is $1.00 USD. If a payment request is limited to either of these tenders, or if the hosted payment experience is expected to present either of these tenders as an available option, use a `requestedAmount` of `1.00` or higher.

This minimum is separate from any network fee the payer's wallet may require to submit the transaction.

#### Sandbox transaction amount limit

In Sandbox, the test server may enforce a $100 maximum transaction amount. If you send a `requestedAmount` greater than `100.00`, the API may return `400 Bad Request` with a message similar to:

```
The requested payment amount exceeds the terminal's maximum limit.
```

This is expected Sandbox behavior. Use amounts at or below `100.00` for normal happy-path testing.

You should also test your integration's error handling by intentionally sending a payment amount above the limit and confirming that your point-of-sale or checkout experience displays a clear message to the merchant.

In Production, maximum transaction limits can vary by merchant and terminal configuration and are typically based on the merchant's approval.

#### Example request body: basic hosted payment

Use this as the default hosted payment example.

```json
{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 25.00,
  "reference": "ORDER-4821"
}
```

#### Example request body: virtual terminal

This example is appropriate for a common virtual terminal checkout flow. The `customer` object is omitted because it is not required by default for virtual terminals.

```json
{
  "terminalId": "{terminalId}",
  "merchantId": "{merchantId}",
  "requestedAmount": 25.00,
  "reference": "ORDER-4821",
  "refundEmail": "customer@example.com"
}
```

#### Example request body: physical terminal

This example is appropriate for a common physical terminal payment flow. The `customer` object is omitted unless your specific tender flow or terminal configuration requires it.

```json
{
  "terminalId": "{terminalId}",
  "merchantId": "{merchantId}",
  "requestedAmount": 25.00,
  "reference": "ORDER-4821"
}
```

#### Example request body: with customer details

Use this pattern only when your payment flow requires customer details or when you intentionally want to pass customer details for reporting, receipts, or operational handling.

```json
{
  "terminalId": "{terminalId}",
  "merchantId": "{merchantId}",
  "requestedAmount": 25.00,
  "reference": "ORDER-4821",
  "refundEmail": "customer@example.com",
  "customer": {
    "firstName": "Jordan",
    "lastName": "Reed",
    "email": "customer@example.com",
    "address": "456 Market St",
    "address2": "Suite 210",
    "city": "Chicago",
    "state": "IL",
    "postalCode": "60601",
    "countryCode": "US"
  }
}
```

#### Example request body: with cart items

Use `cartItems` when you want to pass line-item order context with the payment.

```json
{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 27.50,
  "reference": "ORDER-4821",
  "description": "Coffee subscription order",
  "cartItems": [
    {
      "id": "SKU-COFFEE-12OZ",
      "name": "House Blend Coffee",
      "quantity": 2,
      "price": 12.50
    },
    {
      "id": "SHIP-STANDARD",
      "name": "Standard shipping",
      "quantity": 1,
      "price": 2.50
    }
  ]
}
```

#### Example request body: tender type selection

Use `tenderTypes` when you want to limit the hosted payment experience to a specific tender or tender group.

```json
{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 25.00,
  "reference": "ORDER-4821",
  "description": "Checkout payment",
  "tenderTypes": [
    "usdcBase",
    "usdcSolana"
  ]
}
```

Only pass tender types that are already enabled and available for the merchant, location, and terminal.

#### Example request body: BTC (on-chain) tender selection

Use `bitcoin` when you want to limit the hosted payment experience to BTC (on-chain) / Bitcoin on-chain.

```json
{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 2500.00,
  "reference": "ORDER-4821",
  "description": "Deposit payment",
  "tenderTypes": ["bitcoin"],
  "webhookUrls": [
    "https://example.com/webhooks/bead/payment"
  ]
}
```

BTC (on-chain) payments can remain in `processing` for several minutes or longer. Do not fulfill the order based on `processing`. Wait until the payment reaches `completed`.

#### Example request body: expanded

```json
{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 50.00,
  "reference": "ORDER-4821",
  "description": "Coffee subscription order",
  "cartItems": [
    {
      "id": "SUBSCRIPTION-COFFEE",
      "name": "Coffee subscription",
      "quantity": 1,
      "price": 50.00
    }
  ],
  "customer": {
    "email": "customer@example.com",
    "firstName": "Casey",
    "lastName": "Hart",
    "address": "19 Harbor Point Rd",
    "address2": "Floor 2",
    "city": "Seattle",
    "state": "WA",
    "countryCode": "US",
    "postalCode": "98101"
  },
  "redirectUrl": "https://merchant.example.com/payment-return",
  "emailReceipt": true,
  "smsReceipt": false,
  "refundEmail": "customer@example.com",
  "webhookUrls": [
    "https://merchant.example.com/payments/webhook"
  ]
}
```

Notes:

* `cartItems` can be omitted when you do not have item-level detail.
* Do not send placeholder cart items with blank `id`, blank `quantity`, or blank `price`.
* `customer` can be omitted unless your selected tender, terminal configuration, or compliance flow requires it.
* If you include `customer`, provide all required customer fields.
* `refundEmail` is recommended when you have a payer email available. It is used to send reclaim instructions if the payment ends as `underpaid`, `overpaid`, `expired`, `invalid`, or `cancelled`.
* In Sandbox, keep normal happy-path test amounts at or below `100.00`.

#### Example response

```json
{
  "paymentId": "pay_c10b29e3c4b54d8aa12f9934",
  "trackingId": "c10b29e3c4b54d8aa12f9934",
  "paymentUrls": [
    "https://pay.test.devs.beadpay.io/6539fa89f0363f1722b377ef"
  ]
}
```

#### Key fields

`terminalId` — Terminal identifier in Bead. Identifies the terminal used for the payment and determines terminal-level settings such as the default webhook, terminal type, and tender types available to the hosted payment experience.

`merchantId` — Merchant identifier. Used for reporting and settlement.

`requestedAmount` — Amount in the requested currency. For USDC on Base and USDC on Solana, use `1.00` or higher. In Sandbox, the test server may enforce a maximum transaction amount of `100.00`.

`cartItems` — Optional array of order, invoice, or basket line items associated with the payment. Use this when you want to pass item-level context for reporting, receipts, or reconciliation. If you do not have item-level detail, omit the field.

`tenderTypes` — Optional array used to limit or direct the payment to specific tender types. The selected tender types must already be enabled for the merchant, location, and terminal. Passing `tenderTypes` does not enroll the merchant into a new tender type. Use `bitcoin` for BTC (on-chain) / Bitcoin on-chain payments. Use `bitcoin_Lightning` for Bitcoin Lightning. Do not treat these as the same customer experience. When you limit a payment to BTC (on-chain), design the checkout flow for asynchronous completion: acknowledge `processing`, keep the order or invoice pending, and fulfill only after `completed`. When you limit a payment to USDC on Base or USDC on Solana, the `requestedAmount` must be at least `1.00`.

`reference` — Merchant-side reference such as an order or invoice number.

`description` — Description that may be shown to the customer and in reporting.

`customer` — Optional customer object. Include it only when your selected tender, terminal configuration, compliance flow, or reporting process requires customer details. If included, provide the full required customer fields.

`redirectUrl` — Optional URL Bead redirects to after checkout completes or is cancelled.

`emailReceipt`, `smsReceipt` — Optional receipt delivery settings.

`refundEmail` — Optional email used for reclaim instructions when unconverted crypto must be returned to the payer. Recommended when you already have the payer's email.

`webhookUrls` — Optional array of additional webhook endpoints for this payment.

#### Underpayments and overpayments

In wallet-based crypto payments, the customer typically scans a QR code to set the destination and then manually enters the amount in their wallet app. If they mistype the amount, the payment can end in one of these outcomes:

`underpaid` — the customer sent less than the requested amount

`overpaid` — the customer sent more than the requested amount

Recommended handling:

* Underpaid: treat as unsuccessful. Do not fulfill. The crypto sent by the customer is returned through the reclaim flow. If the customer still wants to pay, create a new payment and start a new hosted checkout.
* Overpaid: treat as unsuccessful. Do not fulfill. The crypto sent by the customer is returned through the reclaim flow. If the customer still wants to pay, create a new payment and start a new hosted checkout.

#### Email behavior for reclaim

If you provide `refundEmail`, Bead emails reclaim instructions when reclaim is required.

If `refundEmail` is not provided, the hosted payment page prompts the payer to enter an email address when an underpaid or overpaid outcome occurs, then Bead emails reclaim instructions.

#### Webhooks

If you include `webhookUrls`, Bead sends payment lifecycle events to both:

* the terminal's default webhook
* each URL in the `webhookUrls` array

Configure at least one webhook endpoint before you depend on webhooks in production. See Payment Webhooks for payload and retry details.

Webhooks are strongly recommended for BTC (on-chain) / Bitcoin on-chain payments. In those flows, the customer may leave the hosted payment page after the payment reaches `processing`, while the merchant still needs to know when the payment reaches `completed`.

Use webhooks to update the merchant order, invoice, or fulfillment workflow when the final payment status is available. Do not rely on the customer keeping a browser tab open until BTC (on-chain) completion.

#### Step 3: Present the hosted page

Use the first URL in `paymentUrls` to start checkout.

Common patterns:

* Web or SPA: embed the URL in an iframe or open a new tab or window
* Native app: load the URL in an in-app browser or webview

Redirect behavior:

* If `redirectUrl` is provided, Bead redirects the browser to `redirectUrl` after success or cancel and includes context in the query string.
* If `redirectUrl` is not provided, Bead displays a hosted confirmation page that indicates success or cancellation.

Regardless of redirect choice, always confirm the final status via webhooks or status polling before shipping goods or granting access.

For BTC (on-chain) / Bitcoin on-chain payments, the customer experience should not depend on the customer waiting on the hosted page until final completion. Once the payment is detected and the status reaches `processing`, show the customer that the transaction is underway and allow them to move to an order, invoice, receipt, or payment-status screen.

Continue to treat the payment as pending until it reaches `completed`.

#### Step 4: Confirm payment status

You can confirm payment status using webhooks or polling.

**Option A: Webhooks**

* Configure a terminal-level webhook and optionally supply `webhookUrls` per payment
* Your server receives JSON payloads whenever `statusCode` changes
* Respond with `200 OK` as soon as you have persisted or queued the event

**Option B: Polling**

Endpoint:

```http
GET https://api.test.devs.beadpay.io/Payments/{paymentId}/tracking
```

Headers:

```http
X-Api-Key: {apiKey}
Accept: application/json
```

Example curl:

```bash
curl -s -X GET "https://api.test.devs.beadpay.io/Payments/{paymentId}/tracking" \
  -H "X-Api-Key: {apiKey}" \
  -H "Accept: application/json"
```

Use the `paymentId` returned from the Create Payment response.

Typical statuses:

* `created` — the payment has been created and Bead is waiting for customer payment
* `processing` — funds have been detected and the payment is underway, but the payment is not final
* `completed` — the payment has fully succeeded; use this as the fulfillment trigger
* `underpaid`, `overpaid`, `expired`, `invalid`, or `cancelled` — non-happy-path outcomes that should not be fulfilled

For BTC (on-chain) / Bitcoin on-chain payments, `processing` can last several minutes or longer. In those flows, tell the customer the BTC payment has been detected, let them move on from the hosted payment experience, and rely on webhooks or polling to detect `completed`.

Do not ship goods, release physical items, grant digital access, or mark an invoice as paid until the status is `completed`.

For full status details, see [Payment Statuses](/payments/payment-statuses).

#### Troubleshooting

**401 Unauthorized**

* Confirm the API key is present.
* Confirm the header name is exactly `X-Api-Key`.
* Confirm you are using the correct terminal API key for the environment.
* Do not use the masked API key value for authentication.

**Hosted page will not load**

* Confirm you are using a URL from the `paymentUrls` array returned by the create payment response.
* Confirm the terminal has at least one active tender type in the environment you are testing.
* Use `GET /Terminals/{terminalId}` to verify the terminal's current `tenderTypes` array.

**Validation error on payment creation**

* Confirm the request body matches the tender and terminal requirements for the flow you are testing.
* If testing USDC on Base or USDC on Solana, confirm `requestedAmount` is at least `1.00`.
* If using `tenderTypes`, confirm the selected tenders are already approved and active for the merchant, location, and terminal.
* Use `GET /Terminals/{terminalId}` to verify the terminal's current `tenderTypes` array.
* If the tender is missing, it may require onboarding, pricing, provider enrollment, or operational support before it can be used.
* If using `customer`, confirm all required customer fields are present.
* If your flow does not require `customer`, omit the object instead of sending a partial object.
* If using `refundEmail`, confirm it is a valid email address.

**Invalid cart item values**

* If you include `cartItems`, confirm each item has complete values for `id`, `quantity`, and `price`.
* Do not send blank placeholder values.
* If you do not have line-item detail, omit `cartItems` from the request.

**Payment amount exceeds terminal maximum limit**

* Confirm `requestedAmount` does not exceed the terminal's configured maximum transaction amount.
* In Sandbox, the test server may enforce a $100 maximum transaction amount.
* A Sandbox request greater than `100.00` may return `400 Bad Request` with a message similar to `The requested payment amount exceeds the terminal's maximum limit.`
* This is expected Sandbox behavior and is a good scenario to test for graceful error handling.
* In Production, maximum transaction limits can vary by merchant and terminal configuration.

**No status change**

* Confirm you are polling the correct `paymentId`.
* If using webhooks, confirm your endpoint is reachable.
* Confirm your webhook endpoint returns HTTP `200` quickly after receiving an event.
* If the payment uses BTC (on-chain) / Bitcoin on-chain and the status is `processing`, the payment may still be waiting for Bitcoin network confirmation. This can take several minutes or longer. Do not create a duplicate payment unless the original payment reaches a final unsuccessful state or the customer intentionally starts over.

#### Suggested checkout error handling

If the API returns a terminal maximum transaction error, show a clear message to the merchant or operator.

Example message:

```
This payment amount exceeds the terminal's maximum allowed transaction amount. Enter a lower amount or contact support if the limit needs to be reviewed.
```

In Sandbox, this can happen when testing amounts above `100.00`.

#### Next steps

After you can create and complete a payment in Sandbox:

* Configure [Payment Webhooks](/payments/payment-webhooks) and verify your endpoint receives status events
* Use [Payment Statuses](/payments/payment-statuses) for ad hoc queries and troubleshooting
* Review [Why do BTC (on-chain) payments take longer?](/faqs-and-troubleshooting/payments-faqs/why-do-btc-on-chain-payments-take-longer) for Bitcoin on-chain timing and customer messaging
* Review [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment) before enabling BTC (on-chain) in physical, digital, invoice, or delayed-fulfillment flows
* Explore [Reporting](/reporting) and [Settlement](/settlement) to build payment history and reconciliation jobs
* When ready for Production, request production credentials and switch API base URLs to Production


# Run a Test Payment in the API Reference

Use the sandbox API Reference to create a test payment directly from your browser.

This is useful when you want to confirm that your Payments API key, merchant ID, terminal ID, and request body are working before building the request in your own application.

#### Before you start

You need:

* A sandbox Payments API key
* A sandbox merchant ID
* A sandbox terminal ID for that merchant

The Payments API key is sent as a request header. Do not add the API key to the request body or URL.

#### What you will do

In this guide, you will:

* Open the `POST /Payments/crypto` endpoint in the sandbox API Reference
* Open the test request panel
* Add your `X-Api-Key` header
* Paste a sample JSON request body
* Update the merchant ID and terminal ID
* Send the request
* Copy the hosted payment URL from the response

#### Step 1: Open the payment endpoint

Go directly to the sandbox API Reference page for creating a payment:

```
https://api.test.devs.beadpay.io/apidocs/index.html#tag/payments/POST/Payments/crypto
```

Find the `POST /Payments/crypto` endpoint.

#### Step 2: Open the test request panel

![](/files/ppOkTSCREYYqsgDuYbiB)

Select `Test Request`.

This opens the interactive API Client, where you can add headers, paste a JSON request body, and send the request.

#### Step 3: Add your API key header

<figure><img src="/files/BFnn4u7Zi1W9Ga7UX6GV" alt=""><figcaption></figcaption></figure>

In the API Client, expand `Headers`.

Add a new header:

| Key         | Value                         |
| ----------- | ----------------------------- |
| `X-Api-Key` | Your sandbox Payments API key |

The API Client may display the header as `x-api-key` after it is added. That is okay. In your own code, use the canonical header name `X-Api-Key`.

Keep the existing JSON headers:

| Key            | Value              |
| -------------- | ------------------ |
| `Content-Type` | `application/json` |
| `Accept`       | `application/json` |

If the Authentication section says no authentication is selected, that is okay for this test. The API key is being added directly as a request header.

#### Step 4: Paste the request body

<figure><img src="/files/NSGTaSZdVXnLkFgfh0Ax" alt=""><figcaption></figcaption></figure>

In `Request Body`, choose `JSON`.

Paste the sample request body below.

Replace:

* `TERMINAL_ID_HERE` with your sandbox terminal ID
* `MERCHANT_ID_HERE` with your sandbox merchant ID

```json
{
  "terminalId": "TERMINAL_ID_HERE",
  "merchantId": "MERCHANT_ID_HERE",
  "requestedAmount": 1.00,
  "refundEmail": "customer@example.com",
  "customer": {
    "firstName": "Jordan",
    "lastName": "Reed",
    "email": "customer@example.com",
    "address": "456 Market St",
    "address2": "Suite 210",
    "city": "Chicago",
    "state": "IL",
    "postalCode": "60601",
    "countryCode": "US"
  }
}
```

Before sending, confirm:

* The terminal ID has been replaced
* The merchant ID has been replaced
* The JSON does not include a trailing comma
* The API key header has been added

\[Screenshot placeholder: Show the API Client with the `X-Api-Key` header, request body, and Send button]

#### Step 5: Send the request

<figure><img src="/files/t9ayKk3zzNN442lpdje2" alt=""><figcaption></figcaption></figure>

Select `Send`.

A successful request returns a `200 OK` response with payment details.

The response includes:

* `paymentId`
* `trackingId`
* `paymentUrls`

Example response:

```json
{
  "paymentId": "pay_b7940046b87a496d9096728cd0cf2dd7",
  "trackingId": "b7940046b87a496d9096728cd0cf2dd7",
  "paymentUrls": [
    "https://pay-test.bead.xyz/69f1fc75404d0e85721b94ff"
  ]
}
```

Copy the first value in `paymentUrls` and open it in a browser to view the hosted payment page.

#### Troubleshooting

**401 Unauthorized**

Confirm that:

* The `X-Api-Key` header was added
* The full sandbox Payments API key value was used
* The API key matches the sandbox environment
* The API key was not added to the request body or URL

**Validation error**

Confirm that:

* The merchant ID and terminal ID are valid sandbox values
* The request body is valid JSON
* The request does not include a trailing comma
* The customer fields are complete for the payment flow you are testing

**Hosted payment URL does not load**

Confirm that:

* You copied a URL from the `paymentUrls` array
* The response was successful
* The terminal is configured for the tender types you are testing

#### Related pages

* [Create Payment](/payments/create-payment)
* [Authentication](/authentication)
* [Payment Statuses](/payments/payment-statuses)
* [Test Crypto Transactions](/payments/test-crypto-transactions)


# Payment Statuses

Integrators can retrieve the current status of a payment at any time using the `paymentId` returned when the payment is created. Use this endpoint to verify completion, support customer issues, and keep internal records accurate.

Payment status checks complement webhooks, Reporting, and Settlement. Webhooks notify you when something changes, this endpoint lets you query on demand, and Reporting and Settlement provide historical and settlement views across many payments.

**Overview**

Use the payment status endpoint when you want to:

* retrieve near real time payment status with a simple GET request
* confirm final outcomes such as `completed`, `expired`, `underpaid`, `overpaid`, or `invalid`
* identify the tender type actually used to pay, such as `bitcoin_Lightning`, `usdcSolana`, `paypal`, `venmo`, or `klarna`
* troubleshoot or support a single payment without pulling a full history

For ongoing monitoring and reconciliation, prefer webhooks plus Reporting and Settlement instead of frequent polling.

**Requesting payment status**

Payment status is retrieved by `paymentId`.

**Endpoint**

`GET /Payments/{paymentId}/tracking`

**Path parameters**

| Name        | Type   | Required | Description                                                |
| ----------- | ------ | -------- | ---------------------------------------------------------- |
| `paymentId` | string | Yes      | Payment identifier returned by the Create Payment response |

> `trackingId` is a legacy identifier that is deprecated but still supported for backwards compatibility. The legacy endpoint `GET /Payments/tracking/{trackingId}` continues to work. New integrations should use `paymentId` and `GET /Payments/{paymentId}/tracking`.

**Required headers**

Payments endpoints use header based authentication with a terminal API key.

| Header      | Value              |
| ----------- | ------------------ |
| `X-Api-Key` | `{apiKey}`         |
| `Accept`    | `application/json` |

Notes:

* the API key is the secret credential
* the masked API key is not usable
* send the full API key value in the `X-Api-Key` header on every Payments request

**Authentication methods for Payment Statuses**

**Using `X-Api-Key`**

Use `X-Api-Key` for new Payments integrations.

```http
GET /Payments/{paymentId}/tracking
X-Api-Key: {apiKey}
Accept: application/json
```

**Using OAuth for legacy Payments integrations**

If you are using an existing integration that authenticates Payments through OAuth password grant, send the access token as:

```http
GET /Payments/{paymentId}/tracking
Authorization: Bearer {access_token}
Accept: application/json
```

New integrations should use the `X-Api-Key` method.

**Example request**

```bash
curl "https://api.test.devs.beadpay.io/Payments/{paymentId}/tracking" \
-H "X-Api-Key: {apiKey}" \
-H "Accept: application/json"
```

**Example response**

The endpoint returns a Payment object. The full response in your environment may include additional fields such as payment instructions, conversion details, transactions, notifications, settlement status, and metadata.

The example below is abbreviated to highlight the fields most commonly used for status handling. For completed payments, `tenderType` identifies the tender actually used to pay. The response uses `created` and `updated`; it does not include a `completedAt` field.

```json
{
  "id": "65f0b274f9a4c9a3f8d4b2a1",
  "terminalId": "65f0b274f9a4c9a3f8d4b2a1",
  "paymentCode": "PAY-9X7K2M",
  "paymentId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
  "trackingId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
  "statusCode": "completed",
  "tenderType": "bitcoin_Lightning",
  "reqCurrencyId": 1,
  "payCurrencyId": 3,
  "amounts": {
    "requested": {
      "inRequestedCurrency": {
        "amount": 100.00,
        "amountPrecision": 2,
        "currencyId": 1
      },
      "inPaymentCurrency": {
        "amount": 0.00154321,
        "amountPrecision": 8,
        "currencyId": 3
      }
    },
    "paid": {
      "inPaymentCurrency": {
        "amount": 0.00154321,
        "amountPrecision": 8,
        "currencyId": 3
      }
    },
    "settleableAmount": {
      "amount": 100.00,
      "amountPrecision": 2,
      "currencyId": 1
    },
    "partnerFees": []
  },
  "reference": "ORDER123",
  "created": "2025-03-04T10:15:30Z",
  "updated": "2025-03-04T10:20:30Z"
}
```

**Using `tenderType`**

Use the returned `tenderType` to identify the tender actually used for the payment.

This is especially important when the payment flow presents multiple tender options. For example, an integrator may create one hosted payment session that allows several eligible tenders. When the customer completes the payment with Bitcoin Lightning, the status response returns:

```json
{
  "statusCode": "completed",
  "tenderType": "bitcoin_Lightning"
}
```

Recommended handling:

* store `tenderType` with your order, invoice, or payment record
* use `tenderType` for support, analytics, and tender-level reporting
* use the returned value as the actual tender paid, not only the tender options offered when the payment was created
* expect `tenderType` to be populated for completed payments
* for early or unresolved states, such as `created`, `tenderType` may not yet identify a completed tender

**BTC Classic and `tenderType`**

BTC Classic refers to Bitcoin on-chain payments. In API responses, use the returned `tenderType` value to identify the tender actually used for the payment.

For Bitcoin on-chain payments, the tender type is `bitcoin`. Do not treat `bitcoin_Lightning` as BTC Classic; Bitcoin Lightning is a separate, faster BTC payment rail.

If your payment flow allows multiple tenders, wait for the returned `tenderType` when available. If your integration already knows that the customer selected BTC Classic, you can use that local checkout context for customer messaging, but still use `statusCode` as the source of truth for fulfillment.

Recommended handling:

* use `tenderType: "bitcoin"` for BTC Classic / Bitcoin on-chain handling
* use `tenderType: "bitcoin_Lightning"` for Bitcoin Lightning handling
* do not assume all BTC payments have the same customer wait time
* use `completed`, not `processing`, as the fulfillment trigger

**Status values**

Statuses are returned in the `statusCode` field and use the `PaymentStatus` enum.

Statuses marked as reserved are not currently returned by the API but are documented for future compatibility.

**How crypto amount mismatch happens**

In most wallet based crypto payments, the customer scans the QR code to set the destination and then manually enters the amount to send in their wallet app. If the customer mistypes the amount, the payment can end as:

* `underpaid` when less than the requested amount is sent
* `overpaid` when more than the requested amount is sent

For both `underpaid` and `overpaid`, the payment is not treated as successful. The crypto that was paid remains unconverted and is returned to the payer through Bead's reclaim process.

**Status codes**

| Status code         | Description                                                                                                                                                                                                                                                                                                                                          |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `created`           | Payment record created, waiting for customer funds. The actual paid tender may not yet be known.                                                                                                                                                                                                                                                     |
| `processing`        | Funds detected and conversion or settlement processing is in progress. The payment is underway, but it is not final. For most tenders, this state may be brief. For BTC Classic / Bitcoin on-chain payments, this state may last several minutes or longer while the Bitcoin network confirms the transaction. Do not fulfill based on `processing`. |
| `completed`         | Customer sent the requested amount. `tenderType` identifies the tender actually used. Crypto, if any, has been converted and credited. Treat this as the successful final payment state and the fulfillment trigger.                                                                                                                                 |
| `underpaid`         | Customer sent less than requested. The payment should be treated as not completed. The crypto that was paid remains unconverted and will be returned to the payer through the reclaim process.                                                                                                                                                       |
| `overpaid`          | Customer sent more than requested. The payment should be treated as not completed. The crypto that was paid remains unconverted and will be returned to the payer through the reclaim process.                                                                                                                                                       |
| `expired`           | No funds received before the payment window expired. Any funds sent later remain unconverted and must be reclaimed.                                                                                                                                                                                                                                  |
| `invalid`           | Irregular event such as multiple transactions, unsupported asset, compliance block, or extreme market movement. Funds remain unconverted and must be reclaimed.                                                                                                                                                                                      |
| `cancelled`         | Customer or merchant cancelled the payment. Any funds remain unconverted and may be reclaimed.                                                                                                                                                                                                                                                       |
| `fullyRefunded`     | Reserved. Will indicate that the entire payment has been refunded.                                                                                                                                                                                                                                                                                   |
| `partiallyRefunded` | Reserved. Will indicate that a portion of the payment has been refunded.                                                                                                                                                                                                                                                                             |

**Status finality**

Bead payment statuses fall into two categories: stateful and final.

**Stateful statuses** are transitional. A payment in a stateful status may advance to a new status, and your webhook consumer should expect to receive additional events as the payment progresses.

* `created` — payment is open, waiting for the customer to initiate funds
* `processing` — funds detected; conversion or settlement is underway

**Final statuses** are terminal. Once a payment reaches a final status, it will not transition again. No corrective, superseding, or follow-on webhook will be sent for that `paymentId`. Treat the first final-status event as the authoritative outcome.

* `completed`
* `underpaid`
* `overpaid`
* `expired`
* `invalid`
* `cancelled`

Design your webhook consumer to handle multiple events per `paymentId` during the stateful phase, and to treat the first final status as the definitive result. A `completed` webhook will never be followed by a `cancelled` webhook for the same payment, and a `cancelled` webhook will never be revised to `completed`.

**Integrator handling guidance**

Use these rules when mapping payment statuses to your internal order or invoice state.

**`processing`**

Treat as payment detected, but not complete.

A `processing` status means Bead has detected funds and the payment is underway. It does not mean the payment has reached its successful final state.

Do not fulfill the order, release goods, grant digital access, ship an item, or mark an invoice as paid based only on `processing`.

For most tender types, `processing` may be short enough that the customer can remain in the payment flow until the final status is returned.

For BTC Classic / Bitcoin on-chain payments, `processing` can last several minutes or longer. In those flows, the recommended customer experience is to acknowledge that the payment has been detected, let the customer move on, and use webhooks or this endpoint to confirm when the payment reaches `completed`.

Recommended BTC Classic handling:

* show the customer that the BTC payment has been detected
* explain that Bitcoin on-chain confirmation can take several minutes
* move the customer to an order, invoice, receipt, or status screen
* keep the internal order or invoice in a pending payment state
* use Payment Webhooks or this endpoint to detect the final status
* fulfill only after the payment reaches `completed`

Example customer message:

```
Your BTC payment has been detected and is now processing.

Bitcoin on-chain payments can take several minutes to confirm. You do not need to keep this page open. The merchant will confirm when the payment is complete.
```

**`completed`**

* Treat as successful payment.
* Store the returned `tenderType` as the tender actually paid.
* Proceed with fulfillment after you have confirmed the final state via webhooks or this endpoint.
* Use `paymentId`, `statusCode`, `tenderType`, `reference`, `created`, and `updated` to support order lookup and customer support workflows.

**`underpaid`**

* Treat as not successful payment.
* The transaction is not going through. Do not fulfill the order based on an `underpaid` status.
* The crypto that was paid remains unconverted and will be returned to the payer through Bead's reclaim process.
* Prompt the customer to attempt payment again with a new hosted payment session, based on your normal retry or tender selection flow.

**`overpaid`**

* Treat as not successful payment.
* The transaction is not going through. Do not fulfill the order based on an `overpaid` status.
* The requested amount is not processed as a successful payment, and the overage is not split out separately. The crypto that was paid remains unconverted and will be returned to the payer through Bead's reclaim process.
* Prompt the customer to attempt payment again with a new hosted payment session, based on your normal retry or tender selection flow.

**`expired`, `invalid`, `cancelled`**

* Treat as not successful payment.
* Do not fulfill the order.
* If funds were sent, they remain unconverted and are handled through reclaim.

For how these states behave in full flows, see the Payment Scenarios pages, which walk through underpaid, overpaid, and reclaim flows in more detail.

**Reclaiming unconverted crypto**

When a payment ends in one of the following states:

* `underpaid`
* `overpaid`
* `expired`
* `invalid`
* `cancelled`

any unconverted crypto associated with that payment is handled through Bead's reclaim process.

For `underpaid` and `overpaid`, the full crypto amount paid by the customer is returned through reclaim. The payment should not be treated as successful, and the integrator should not fulfill the order based on either status.

**Email capture behavior**

Bead uses an email based reclaim flow to return unconverted crypto.

* if an email address is included in the original payment request, common in digital and `virtual` terminal flows, Bead will email instructions to the payer when reclaim is required
* if an email address is not included in the request, common in `physical` or terminal based flows, the hosted payment page will prompt the customer to enter an email address when one of these outcomes occurs, then Bead emails the reclaim instructions

Your integration does not need to implement the reclaim process directly, but you should:

* display clear messaging in your UI when these statuses occur
* add internal flags or notes so support and finance teams can recognize reclaim scenarios
* avoid automatically retrying or recreating payments without clear customer intention
* create a new payment session when the customer is ready to attempt payment again

**Error responses**

**Common error responses**

| Code  | Condition                                                                                                                                                                                |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401` | Missing or invalid API key. Confirm the `X-Api-Key` header is present and the key is valid for this environment.                                                                         |
| `403` | Authenticated, but not permitted to view this payment in the current context. Confirm you are using the correct API key for the terminal and merchant relationship tied to this payment. |
| `404` | No payment exists for this `paymentId`, or it is not visible in the current context.                                                                                                     |

Log the response body and headers for debugging, but avoid exposing raw error text directly to end users.

**Best practices**

Recommended patterns when using the payment status endpoint:

**Prefer webhooks for real time changes**

Configure Payment Webhooks so your system receives status updates automatically. Use this endpoint for ad hoc checks and support workflows, not as your primary real time mechanism.

Webhooks are especially important for longer-running payment flows such as BTC Classic / Bitcoin on-chain. In those flows, the customer may leave the payment page after the payment reaches `processing`, while the merchant still needs to know when the payment reaches `completed`.

Recommended pattern:

* use the hosted payment page or status endpoint to show the customer the current payment state
* use webhooks to update the merchant order, invoice, or fulfillment workflow
* use this endpoint for customer support, reconciliation checks, or manual refresh actions
* avoid relying on the customer keeping a browser tab open until final completion

**Use `paymentId` as the primary lookup key**

Save the `paymentId` from the Create Payment response alongside your order or invoice. Use it to correlate status checks, webhooks, and Reporting records. `trackingId` is a legacy identifier that is deprecated but still supported for backwards compatibility; new integrations should use `paymentId`.

**Store the actual paid `tenderType`**

Save the returned `tenderType` after the payment reaches a final state. This lets your system distinguish between the tenders offered during checkout and the tender actually used to pay.

For example, if a hosted payment flow allowed multiple tenders but the customer paid with Bitcoin Lightning, the completed payment status returns `tenderType: "bitcoin_Lightning"`.

**Map status codes to internal order states**

Translate `completed`, `underpaid`, `overpaid`, `expired`, `invalid`, and `cancelled` into internal states that your business and support team understand.

Only `completed` should be treated as a successful payment. Statuses such as `underpaid` and `overpaid` should be treated as not successful and routed through the appropriate retry and reclaim messaging.

**Use a pending state for BTC Classic processing**

For BTC Classic / Bitcoin on-chain payments, map `processing` to an internal pending state such as:

* payment detected
* pending confirmation
* awaiting BTC confirmation
* payment processing

Do not map BTC Classic `processing` to paid, approved, fulfilled, or complete.

Recommended mapping:

| Bead status  | Stateful or final | Internal state                          | Fulfillment behavior    |
| ------------ | ----------------- | --------------------------------------- | ----------------------- |
| `created`    | Stateful          | Awaiting payment                        | Do not fulfill          |
| `processing` | Stateful          | Payment detected / pending confirmation | Do not fulfill          |
| `completed`  | Final             | Paid                                    | Fulfillment may proceed |
| `underpaid`  | Final             | Payment failed / reclaim required       | Do not fulfill          |
| `overpaid`   | Final             | Payment failed / reclaim required       | Do not fulfill          |
| `expired`    | Final             | Payment expired                         | Do not fulfill          |
| `invalid`    | Final             | Payment invalid / review required       | Do not fulfill          |
| `cancelled`  | Final             | Payment cancelled                       | Do not fulfill          |

**Avoid tight polling loops**

If you must poll, use reasonable intervals and a backoff strategy. For anything beyond small scale, move to webhooks together with Reporting and Settlement.

**Combine with Reporting and Settlement for reconciliation**

Use this endpoint for single payment checks. For daily reconciliation and accounting, rely on the Reporting payment history endpoints and the Settlement APIs to see payments, batches, and payment settlement lines together.

**Next steps**

* Configure [Payment Webhooks](/payments/payment-webhooks) for real time payment events.
* Review [Tender Types](/reference-guide/enumerations-and-schemas/tender-types) for the list of valid `tenderType` values.
* Review [Why do BTC (on-chain) payments take longer?](/faqs-and-troubleshooting/payments-faqs/why-do-btc-on-chain-payments-take-longer) for more detail on Bitcoin on-chain confirmation timing and customer messaging.
* Review [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment) for guidance on when BTC Classic is appropriate for physical, digital, invoice, and delayed-fulfillment flows.
* Review [Payment Scenarios](/reference-guide/payment-scenarios) for practical examples of handling non-happy-path statuses such as underpaid and overpaid.
* Use [Reporting](/reporting) and [Settlement](/settlement) to build dashboards and reconciliation jobs that operate over many payments at once.

**Legacy authentication**

Some existing Payments integrations authenticate using OAuth password grant and send an access token as:

```http
Authorization: Bearer {access_token}
```

New integrations should use the terminal API key method described above. If you are on the legacy model, see [Authentication](/authentication).


# Payment Status Response Examples

This page provides representative payment status payloads for common Bead payment outcomes.

The same status payload shape is used when you retrieve a payment status directly and when Bead sends a payment status webhook to your system.

Use these examples when mapping Bead payment outcomes to your internal order, invoice, or support states.

### Status payload references

Use the tracking endpoint when you need to check the current status of a specific payment on demand.

```
GET /Payments/{id}/tracking
X-Api-Key: {apiKey}
Accept: application/json
```

Bead also sends payment status webhook events to the configured webhook URL for the terminal or payment. Your webhook handler should process the request body using the same status payload structure shown in the examples below.

```
POST {yourWebhookUrl}
Content-Type: application/json
```

### Payload shape

The status payload includes identifiers, the current payment state, amount information, and optional merchant-supplied metadata.

```json
{
  "paymentId": "string",
  "trackingId": "string",
  "paymentCode": "string",
  "statusCode": "created | processing | completed | underpaid | overpaid | expired | invalid | cancelled",
  "amounts": {
    "requested": {
      "inPaymentCurrency": {
        "amount": 0,
        "currency": null
      },
      "inRequestedCurrency": {
        "amount": 0,
        "currency": {
          "id": 1,
          "code": "USD",
          "name": "US Dollar",
          "symbol": "$"
        }
      }
    },
    "paid": {
      "inPaymentCurrency": {
        "amount": 0,
        "currency": null
      }
    }
  },
  "reference": "string",
  "description": "string",
  "receivedTime": "2026-05-29T17:10:01Z",
  "terminalId": "string",
  "merchantId": "string",
  "errorMessage": null
}
```

### Field notes

| Field                                   | Description                                                                                                                       |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `paymentId`                             | Bead payment identifier. Use this value with `GET /Payments/{id}/tracking`.                                                       |
| `trackingId`                            | Tracking identifier returned when the payment was created. Store this with your internal order or invoice record for correlation. |
| `paymentCode`                           | Short payment code shown on the hosted payment page and in reporting.                                                             |
| `statusCode`                            | Current Bead payment status. Use this as the source of truth for payment state.                                                   |
| `amounts.requested.inRequestedCurrency` | Amount requested from the customer in the original requested currency.                                                            |
| `amounts.requested.inPaymentCurrency`   | Amount expected in the selected payment currency, when available.                                                                 |
| `amounts.paid.inPaymentCurrency`        | Amount actually received in the payment currency.                                                                                 |
| `reference`                             | Optional merchant-supplied reference from the original payment request.                                                           |
| `description`                           | Optional merchant-supplied description from the original payment request.                                                         |
| `receivedTime`                          | Timestamp associated with the status event or update.                                                                             |
| `terminalId`                            | Terminal associated with the payment.                                                                                             |
| `merchantId`                            | Merchant associated with the payment.                                                                                             |
| `errorMessage`                          | Additional error context when available. Usually null for normal lifecycle states.                                                |

### Status examples covered

| Status       | What the example shows                                              |
| ------------ | ------------------------------------------------------------------- |
| `created`    | Payment record created; no funds confirmed yet                      |
| `processing` | Payment is in progress; final outcome is not yet known              |
| `completed`  | Customer paid the requested amount                                  |
| `underpaid`  | Customer sent less than the requested amount                        |
| `overpaid`   | Customer sent more than the requested amount                        |
| `expired`    | Payment expired before completion                                   |
| `cancelled`  | Payment was cancelled before completion                             |
| `invalid`    | Irregular or unsupported payment event requiring exception handling |

Refund statuses are not shown on this page.

### Created

A `created` payment has been created, but the customer has not completed payment and no funds have been confirmed.

### Processing

A `processing` payment is still in progress. The customer or wallet flow may have started, but the final payment outcome is not yet known.

Do not treat `processing` as a successful final state.

### Completed

A `completed` payment indicates that the customer paid the requested amount.

### Underpaid

An `underpaid` payment means the customer sent less than the requested amount.

The `requested` and `paid` objects show the difference between the amount expected and the amount actually received.

Do not treat `underpaid` as a successful payment. The crypto that was paid remains unconverted and will be returned to the payer through the reclaim process.

### Overpaid

An `overpaid` payment means the customer sent more than the requested amount.

The `requested` and `paid` objects show the difference between the amount expected and the amount actually received.

Do not treat `overpaid` as a successful payment. The full crypto amount paid by the customer remains unconverted and will be returned to the payer through the reclaim process. No portion is converted or settled to the merchant. If the customer still wants to complete the purchase, create a new payment.

### Expired

An `expired` payment means the payment window expired before the payment completed.

### Cancelled

A `cancelled` payment means the payment was cancelled before completion.

### Invalid

An `invalid` payment indicates an irregular or unsupported payment event. Examples may include multiple transactions, unsupported assets, compliance blocks, extreme market movement, or other exception conditions.

The exact `errorMessage` may vary by cause.

### Implementation guidance

When mapping these payloads to your integration:

* Use `statusCode` as the primary state indicator
* Store both `paymentId` and `trackingId` with your internal payment, invoice, or order record
* Use `paymentId` when calling `GET /Payments/{id}/tracking`
* Use `trackingId` to correlate Create Payment responses, webhook events, reporting records, and support workflows
* Compare `amounts.requested` and `amounts.paid` when handling `underpaid` or `overpaid`
* Treat `processing` as a non-final state
* Treat `completed` as successful for the requested amount
* Treat `underpaid`, `overpaid`, `expired`, `invalid`, and `cancelled` as not successful for fulfillment
* Make webhook processing idempotent — you may receive the same event more than once
* If a webhook event is missed, delayed, or received out of order, call `GET /Payments/{id}/tracking` to confirm the current state

### Related pages

* [Payment Statuses](/payments/payment-statuses)
* [Payment Webhooks](/payments/payment-webhooks)
* [Payment Scenarios](/reference-guide/payment-scenarios)
* [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto)


# Crypto Wallet Flow and Amounts

Bead’s crypto payment experience uses a direct wallet-to-wallet transfer on chain.

Depending on the asset and network, the consumer experience differs:

* For many on-chain assets and networks, the QR code contains only a destination wallet address. In these cases, the consumer is expected to enter the amount manually in their wallet. This is normal and expected behavior.
* Some rails, such as Bitcoin Lightning, commonly support a scannable invoice format that can include both the destination and the amount. In those cases, the wallet can populate the amount automatically.
* BTC (on-chain) refers to Bitcoin on-chain payments. BTC (on-chain) is different from Bitcoin Lightning. BTC (on-chain) may take several minutes or longer to reach final completion because the payment depends on Bitcoin network confirmation.

For BTC (on-chain), integrators should design the customer experience so the customer can move on after the payment is detected and the status reaches `processing`. The merchant should still wait for `completed` before fulfilling the order, releasing goods, granting digital access, shipping, or marking an invoice as paid.

### Hosted payment page ownership

When an integrator routes a consumer into the Bead hosted payment page for crypto, Bead controls that consumer experience end to end.

Integrators do not control the hosted page UI, content, or flow.

### Integrator-owned pre-payment and post-payment experience

Bead controls the hosted crypto payment page experience. Integrators still control the customer experience before redirecting the customer to Bead and after receiving payment status updates.

Before redirecting the customer, set expectations about the selected tender. For many on-chain payments, the customer may need to manually enter the payment amount in their wallet. For BTC (on-chain) / Bitcoin on-chain, tell the customer that payment confirmation can take several minutes and that the merchant will confirm completion.

After the payment is detected, your integration should use Payment Webhooks or payment status checks to update the merchant order, invoice, or fulfillment workflow.

For BTC (on-chain), do not depend on the customer keeping the payment page open until final completion.

### How the QR code works

#### What the QR code contains

For many crypto payments, the QR code contains:

* a destination wallet address

It typically does not include:

* the amount to send

Because the amount is not encoded in the QR code, the consumer will enter the amount in their wallet based on the amount shown on the payment screen.

#### What the consumer does after scanning

After scanning the QR code, the consumer typically:

* opens their wallet
* selects the same asset and network they chose on the payment screen
* confirms the destination address
* enters the amount shown on the payment screen
* submits the transfer

The consumer’s wallet is the system of record for the transfer submission. The consumer controls the final send action, including the entered amount.

### What happens after the consumer sends the transfer

After the consumer submits the transfer from their wallet, Bead monitors the payment for funds and status changes.

A crypto payment may move through these states:

* `created` — Bead is waiting for the customer to send funds
* `processing` — funds have been detected and the payment is underway, but not final
* `completed` — the payment has reached the successful final state

For most crypto tenders, the `processing` window may be short enough for the customer to remain in the payment flow.

For BTC (on-chain) / Bitcoin on-chain, `processing` can last several minutes or longer. In those flows, the recommended experience is to acknowledge that the payment has been detected, let the customer move on, and use webhooks or status checks to confirm when the payment reaches `completed`.

Do not fulfill based on `processing`. Fulfill only after `completed`.

### Why amount entry varies by rail

Different crypto rails support different payment payloads and wallet behaviors.

#### Bitcoin Lightning example

Bitcoin Lightning commonly uses an invoice-style payload that can include:

* destination
* amount

This can streamline the scan-and-pay experience because the wallet can populate the amount automatically from the invoice.

#### BTC (on-chain) / Bitcoin on-chain example

BTC (on-chain) uses Bitcoin on-chain payments. The customer sends BTC from their wallet to the address shown on the payment page.

The customer may need to:

* scan or copy the destination address
* confirm they are sending BTC on the Bitcoin network
* enter the amount shown on the payment page
* submit the transfer in their wallet

After the transfer is detected, the payment may enter `processing` while Bead waits for the required Bitcoin confirmation behavior. This can take several minutes or longer.

For BTC (on-chain), the customer does not need to remain on the payment page until final completion if your integration can show a pending payment state and rely on webhooks or status checks.

#### On-chain transfer example

For many on-chain transfers, the common pattern is:

* destination address in the QR code
* amount manually entered by the consumer in their wallet

This is expected behavior across many wallets and networks.

### What integrators should expect

#### Consumer experience expectations

If your checkout flow includes crypto, set the expectation that:

* scanning the QR code routes the consumer to a destination address
* for many assets and networks, the consumer will enter the amount shown on the payment screen
* the consumer must send using the same asset and network selected on the payment screen
* for BTC (on-chain) / Bitcoin on-chain, final completion may take several minutes or longer after the transfer is detected
* `processing` means the payment is underway, but not final
* the merchant should fulfill only after the payment reaches `completed`
* for BTC (on-chain), the customer can usually move to an order, invoice, receipt, or status screen after the payment is detected

If your integration supports BTC (on-chain), avoid messaging that requires the customer to wait on the payment page until final completion. Instead, tell the customer when the payment has been detected, explain that Bitcoin on-chain confirmation may take several minutes, and provide a next step such as an order status page, invoice page, receipt page, or merchant confirmation message.

#### Common mismatch scenarios

Manual entry introduces a risk of mismatch. Common causes include:

* amount mistyped
* wrong asset selected in the wallet
* wrong network selected in the wallet
* consumer sends from an exchange flow that introduces delays or uses a different send pattern

Bead’s payment tracking and statuses reflect whether the received transfer matches what was requested.

#### Ways to reduce miskeys and friction

Integrators can reduce issues by:

* setting expectations in their own checkout flow before redirecting to the Bead hosted page, such as a short note that the consumer may need to enter the amount in their wallet
* reminding the consumer to use the same asset and network selected on the payment screen
* reminding the consumer to enter the exact amount shown on the payment screen
* ensuring support teams understand common mismatch scenarios and the likely root causes
* linking consumers to troubleshooting guidance when needed
* for BTC (on-chain), giving the customer clear next-step messaging once payment is detected, so they understand that the transaction is underway even if final completion takes longer

### BTC (on-chain) customer messaging

For BTC (on-chain) / Bitcoin on-chain payments, the customer may need more guidance than they would need for faster tender types.

Recommended message when the customer selects BTC (on-chain):

```
BTC (on-chain) uses Bitcoin on-chain payment confirmation.

After you send the payment, it may take several minutes to complete. The merchant will confirm when the payment is complete.
```

Recommended message when the payment reaches `processing`:

```
Payment detected.

Your BTC payment is now processing on the Bitcoin network. This can take several minutes. You do not need to keep this page open.
```

Recommended message when fulfillment must wait:

```
Your payment is pending confirmation.

The merchant will complete your order once the BTC payment is confirmed.
```

### Roadmap note

Because Bead owns the hosted crypto payment page experience end to end, we can evolve the experience over time without requiring changes from integrators.

We are actively exploring improvements to minimize friction and improve consumer success, including:

* clearer on-screen guidance and coaching for asset, network, and amount entry
* UX enhancements that reduce common miskeys
* technical enhancements that streamline scan-and-send behavior where supported by wallets and networks
* improved messaging for longer-running payment flows such as BTC (on-chain)

This page will be updated as capabilities evolve.

### Common questions

#### Will the consumer always enter the amount?

Not always. It depends on the coin and network.

For many on-chain assets and networks, yes. The consumer enters the amount in their wallet after scanning a destination address. This is expected behavior.

Some rails, such as Bitcoin Lightning, commonly support a scannable format that can include the amount, which can allow the wallet to populate it automatically.

#### Why does Bead show an amount if the QR code does not include it?

Bead shows the intended amount so the consumer knows what to send. The transfer itself is still initiated and confirmed in the consumer’s wallet.

#### What should I tell a consumer who is confused?

Use simple guidance:

* Scan the QR code
* In your wallet, select the same asset and network
* Enter the exact amount shown on screen
* Send the transfer
* Return to the merchant screen

For most tenders, the consumer may see the final payment result shortly after sending.

For BTC (on-chain) / Bitcoin on-chain, the payment may be detected first and then remain in `processing` while the Bitcoin network confirms the transaction. In that case, tell the consumer that the payment is underway and that the merchant will confirm when it is complete.

#### Why does BTC (on-chain) stay processing after the consumer sends payment?

BTC (on-chain) uses Bitcoin on-chain payments. After the consumer sends BTC, the transaction may be detected before it reaches final completion.

When the status is `processing`, Bead has detected the payment and the transaction is underway. The payment is not final until it reaches `completed`.

For BTC (on-chain), integrators should show the customer that the payment has been detected, allow them to move on from the payment screen, and use Payment Webhooks or status checks to confirm final completion.

Do not release goods, grant digital access, ship an order, or mark an invoice as paid until the payment reaches `completed`.

#### Can the consumer close the page after sending BTC (on-chain)?

If the payment has reached `processing`, the transaction has been detected and is underway. In BTC (on-chain) flows, it is generally reasonable to tell the customer they can move on, as long as your integration can track final completion through Payment Webhooks or payment status checks.

The merchant should still wait for `completed` before fulfillment.

#### Should I create a new payment if BTC (on-chain) is still processing?

No, not just because the payment is still `processing`.

BTC (on-chain) can remain in `processing` for several minutes or longer. Do not create a duplicate payment unless the original payment reaches a final unsuccessful state, expires, is canceled, or the customer intentionally starts over.

### Related pages

* [Payment Statuses](/payments/payment-statuses)
* [Payment Webhooks](/payments/payment-webhooks)
* [Create Payment](/payments/create-payment)
* [Why do BTC (on-chain) payments take longer?](/faqs-and-troubleshooting/payments-faqs/why-do-btc-on-chain-payments-take-longer)
* [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment)


# Payment Webhooks

Payment webhooks let Bead send real-time payment status updates to your system. Instead of polling, your server receives an HTTP `POST` whenever a payment's `statusCode` changes.

Payment webhook notifications are typically used together with the Payments and Reporting APIs.

**When to use payment webhooks**

Use payment webhooks when you want to:

* update orders or invoices as soon as a payment completes
* react to `underpaid`, `overpaid`, `expired`, `invalid`, or `cancelled` outcomes
* trigger downstream workflows such as fulfilment, refunds, customer notifications, or support tickets
* track payments without polling `GET /Payments/{paymentId}/tracking`

**How payment webhooks work**

1. You configure a default webhook URL for a terminal, or provide `webhookUrls` when creating a payment.
2. Bead delivers an HTTP `POST` when a payment status changes.
3. Your server verifies the webhook signature using the stored `signingSecret`.
4. Your system records the event and updates internal state.
5. If needed, your system confirms the latest state using `GET /Payments/{paymentId}/tracking`.

**Configure payment webhook delivery**

Payment webhooks are configured per terminal.

**Default terminal webhook**

Use the terminal webhook endpoint to set the default payment webhook URL for a terminal:

`PUT /Terminals/{id}/webhook`

The webhook URL should be an HTTPS endpoint that you control.

A successful setup response includes:

* `url`
* `signingSecret`

Store `signingSecret` securely. You will use it to verify incoming payment webhooks.

**Optional per-payment webhook URLs**

When creating a payment with `POST /Payments/crypto`, you can also provide `webhookUrls` for payment-specific delivery in addition to the terminal's default webhook.

Use this when you want a payment to notify a specific backend flow without changing the terminal's default webhook configuration.

**Where to get the signing secret**

The payment webhook signing secret is associated with the terminal webhook configuration and should be stored securely when the webhook is set.

Important notes:

* do not expose `signingSecret` in client-side code
* do not expect the incoming webhook payload to include the secret
* do not compare the incoming signature header directly to the secret itself

The correct verification pattern is:

1. configure the webhook
2. save the `signingSecret`
3. receive the webhook delivery
4. verify the signature using the steps below
5. only trust the event if verification passes

**Event delivery**

For each payment update, Bead sends a `POST` request to your webhook URL.

General behavior:

* method: `POST`
* content type: `application/json`
* one event per payment status change
* retries occur if your endpoint does not return a successful `2xx` response

Your webhook handler should:

* log every webhook request for debugging and audit
* preserve the raw request body before JSON parsing
* preserve request headers
* verify the signature before processing the payload
* return a successful response quickly after safely persisting or queueing the event

**Current payment signature header**

Current payment webhook deliveries include the signature in:

```
x-webhook-signature: t=<unix_timestamp_ms>,s=<base64_signature>
```

Example:

```
x-webhook-signature: t=1781811428956,s=FK/SW9lIK0CXpNnfweTN3ZbJ8Nvbm1RF69Nm6XE8w3O=
```

<table><thead><tr><th width="85">Field</th><th>Description</th></tr></thead><tbody><tr><td><code>t</code></td><td>Unix epoch timestamp in <strong>milliseconds</strong> when Bead generated the event</td></tr><tr><td><code>s</code></td><td><strong>Base64-encoded</strong> HMAC-SHA256 digest of the signed message <code>t + "." + rawBody</code>, computed using the decoded bytes of the terminal's <code>signingSecret</code></td></tr></tbody></table>

**Verifying the webhook**

Always verify the payment webhook before processing the JSON body.

1. Capture the raw request body exactly as received. Do not parse or reserialize JSON before this step.
2. Read the `x-webhook-signature` header and parse the `t` and `s` values.
3. Validate the timestamp by confirming `t` (in milliseconds) is within 5 minutes of the current time. Reject requests outside this window to prevent replay attacks.
4. Decode `signingSecret` from base64 to raw bytes. Use those bytes as the HMAC key.
5. Construct the signed message by concatenating `t`, a literal period, and the raw request body: `message = t + "." + rawBody`.
6. Compute `HMAC-SHA256(key=decodedSecretBytes, message=message)` and base64-encode the digest.
7. Compare your computed base64 digest to `s` using constant-time equality. Reject the request if they do not match.

For a full code example, see [How do I verify that a webhook really came from Bead?](/faqs-and-troubleshooting/webhooks-and-error-codes/how-do-i-verify-that-a-webhook-really-came-from-bead)

**Example payload**

{% code expandable="true" %}

```json
{
  "paymentId": "pay_d3594f0680964156b21fab60f8573bb4",
  "trackingId": "d3594f0680964156b21fab60f8573bb4",
  "paymentCode": "HM9N44Z43VTW",
  "statusCode": "cancelled",
  "amounts": {
    "requested": {
      "inPaymentCurrency": {
        "amount": 1,
        "currency": {
          "id": 16,
          "code": "USDC_BASE",
          "name": "USDC Base",
          "symbol": "USDC"
        }
      },
      "inRequestedCurrency": {
        "amount": 1,
        "currency": {
          "id": 1,
          "code": "USD",
          "name": "USD",
          "symbol": "$"
        }
      }
    },
    "paid": {
      "inPaymentCurrency": {
        "amount": 0,
        "currency": {
          "id": 16,
          "code": "USDC_BASE",
          "name": "USDC Base",
          "symbol": "USDC"
        }
      }
    }
  },
  "reference": "ORDER123",
  "description": null,
  "receivedTime": "2026-03-30T13:39:29.948491+00:00",
  "terminalId": "69aae38b1f3fe8c6698663dc",
  "merchantId": "664c5e3b0517b0a8a6321c9a",
  "errorMessage": null
}
```

{% endcode %}

**Common payment status values**

Payment webhook events may include status values such as `created`, `processing`, `underpaid`, `overpaid`, `completed`, `expired`, `invalid`, and `cancelled`.

Your system should treat `statusCode` as the primary event classifier.

**Key fields**

**`paymentId`**

This is the primary identifier for the payment. Use it to call `GET /Payments/{paymentId}/tracking` to confirm or refresh payment state and correlate status checks with your internal payment or order record.

**`trackingId`**

This is the tracking identifier returned when the payment was created. Use it to correlate the webhook to your internal payment or order record, reconcile webhook delivery with reporting and support workflows, and key idempotency checks alongside `statusCode`.

**`paymentCode`**

This is the hosted payment code associated with the checkout.

**`statusCode`**

This tells you the payment's current lifecycle state.

**`terminalId`**

This identifies the terminal that produced the payment.

**`merchantId`**

This identifies the merchant associated with the payment.

**`receivedTime`**

This is the timestamp associated with the event payload.

**Recommended processing model**

1. Receive the request.
2. Preserve the raw request body and headers.
3. Read `x-webhook-signature` and parse `t` and `s`.
4. Validate the timestamp — confirm `t` (in milliseconds) is within 5 minutes of the current time.
5. Decode `signingSecret` from base64 to bytes. Build the signed message as `t + "." + rawBody`. Compute `HMAC-SHA256(key=decodedSecretBytes, message=signedMessage)` and base64-encode the digest.
6. Compare the computed digest to `s` using constant-time equality. Reject the request if they do not match.
7. Parse the JSON payload only after verification succeeds.
8. Persist the event or enqueue it for processing.
9. Update your internal order or invoice state based on `statusCode`.
10. Return a successful `2xx` response quickly.

**Idempotency and duplicate handling**

Treat payment webhook delivery as at least once.

A good idempotent processing strategy uses `trackingId` + `statusCode` as the primary key, with the raw body hash or received timestamp optionally stored for debugging.

Your system should be able to safely ignore duplicate deliveries without creating duplicate business actions.

**Delivery mechanics**

* respond quickly, ideally in under one second when possible
* do heavy work asynchronously after the event is safely persisted or queued
* expect retries with backoff when your endpoint fails or times out
* rely on signature verification rather than source IP allowlisting unless Bead explicitly publishes and supports source IP controls for your environment

**Confirming status with the Tracking endpoint**

If you need to confirm the latest state during support, reconciliation, or after a missed event, call the tracking endpoint using the payment's `paymentId`.

**Endpoint**

`GET /Payments/{paymentId}/tracking`

**Headers**

* `X-Api-Key: {apiKey}`
* `Accept: application/json`

**Notes**

* `apiKey` is the secret credential
* `maskedApiKey` is not usable
* the header name must be exactly `X-Api-Key`

**Testing webhooks in Sandbox**

1. Configure a webhook URL for your Sandbox terminal.
2. Create a Sandbox payment with `POST /Payments/crypto`.
3. Complete the payment on the hosted page.
4. Confirm your webhook received payment status updates.
5. Confirm your listener captures `x-webhook-signature`.
6. Verify the signature using the stored `signingSecret`.
7. Optionally confirm the final state using `GET /Payments/{paymentId}/tracking`.

**Troubleshooting**

**I am receiving the webhook but verification fails**

Check:

* you are signing `t + "." + rawBody`, not the raw body alone
* you are decoding `signingSecret` from base64 to bytes before using it as the HMAC key
* you are using the raw request body, not a reserialized JSON body
* you are producing a base64 digest, not a hex digest
* you are using the `signingSecret` for the correct terminal

**Webhook not received**

Check:

* the terminal has a webhook URL configured
* your endpoint is publicly reachable over HTTPS
* your endpoint returns a successful `2xx` response quickly
* your server logs show inbound requests and any verification or parsing failures

**Receiving repeated webhook events**

This usually means your endpoint is timing out or returning a non-`2xx` response. Return success quickly after safely persisting or queueing the event, and make your processing idempotent.

**401 Unauthorized when checking status**

Payments endpoints use `X-Api-Key`. Confirm the API key is present, valid for the environment, and that the header name is exactly `X-Api-Key`.

**403 Forbidden when checking status**

The API key is valid but not permitted for the payment context, or you are mixing environments. Confirm you are using the correct environment base URL and the correct terminal API key.

**Next steps**

* Review [Payment Statuses](/payments/payment-statuses) for the full list of `statusCode` values
* Review [Create Payment](/payments/create-payment) for `webhookUrls` support when you need per-payment delivery
* Review [Webhook Event Reference](/reference-guide/operational-guides/webhook-event-reference) for the shared signature and delivery reference
* Use [Reporting](/reporting) and [Settlement](/settlement) for historical views and reconciliation


# Receipts

## Receipts

Bead payment methods require a small number of fields beyond what you already include on a standard card receipt. This page covers what's required, what's additionally available, and how delivery works.

### Required fields

The following fields must be included on all Bead payment receipts when applicable:

| Field                | When required                           | API source                                                                                      |
| -------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Payment method       | Always                                  | `amounts.paid.inPaymentCurrency.currency.name` on `GET /Payments/{id}` (e.g. Bitcoin Lightning) |
| Final payment status | Always                                  | `statusCode` on the payment object                                                              |
| Exchange rate        | When a crypto-to-fiat conversion occurs | `conversions[].executedExchangeRate` on `GET /Payments/{id}`                                    |

Fields your existing receipt already covers — date, time, amount, merchant name, taxes, support contact — do not need to change.

### The `conversions` array

The `conversions` field is returned as an array on `GET /Payments/{id}` and in the payment result webhook payload. It is present on completed crypto payments and absent on non-crypto payments.

**Array size:** By design, `conversions` will contain exactly one entry for any given payment today. The array type is forward-looking; the system mutates a single conversion entry through its lifecycle rather than appending new ones. You can safely treat it as single-entry, but the defensive pattern below is recommended so your integration is robust to any future changes.

#### Conversion `statusCode`

Each entry in `conversions` has its own `statusCode` field — this is separate from the payment's top-level `statusCode`. The conversion `statusCode` values are:

| Value       | Meaning                                                                    |
| ----------- | -------------------------------------------------------------------------- |
| `quoted`    | A rate has been locked for the payment, but the funds have not yet settled |
| `executed`  | The conversion has completed and the exchange rate is final                |
| `completed` | The conversion and settlement are fully resolved                           |
| `cancelled` | The conversion was cancelled (e.g. payment expired or was invalid)         |

**Only read `executedExchangeRate` once `statusCode` is `executed` or `completed`.** Before that state, this field is zero/default and does not reflect a real rate.

#### Reading the exchange rate

The recommended pattern for reading the exchange rate from the `conversions` array:

1. Check that `conversions` is non-empty.
2. Find the entry where `statusCode` is `"executed"` or `"completed"`. In practice there is only one entry, but keying off `statusCode` rather than position is the correct approach.
3. Read `executedExchangeRate` from that entry.

**Example (JavaScript):**

```javascript
const conversion = payment.conversions?.find(
  c => c.statusCode === "executed" || c.statusCode === "completed"
) ?? payment.conversions?.[0];

const exchangeRate = conversion?.executedExchangeRate;
```

#### Field reference

| Field                   | Notes                                                                                                                                                                                                                                                                                     |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `statusCode`            | Conversion lifecycle state. Read other fields only when this is `executed` or `completed`.                                                                                                                                                                                                |
| `quotedExchangeRate`    | The rate locked at the time the payment was created.                                                                                                                                                                                                                                      |
| `presentedExchangeRate` | The rate shown to the payer at checkout.                                                                                                                                                                                                                                                  |
| `executedExchangeRate`  | The realized settlement rate. **Use this for receipt display.** Today this matches the quoted/presented rate in all normal payment flows; it may differ in edge cases involving payment exceptions (underpaid, cancelled, expired) where funds convert before reaching a suspense wallet. |

### Additional fields available

The following fields are available from `GET /Payments/{id}` and are useful to include on receipts for crypto payments, but are not required by Bead.

| Field                   | API source                                            | Notes                             |
| ----------------------- | ----------------------------------------------------- | --------------------------------- |
| Payment code            | `paymentCode`                                         | Unique identifier for the payment |
| Payment date/time       | `receivedTime`                                        | ISO 8601 timestamp                |
| Crypto amount paid      | `amounts.paid.inPaymentCurrency.amount`               | Amount in the payment currency    |
| Crypto asset            | `amounts.paid.inPaymentCurrency.currency.code`        | e.g. BTC                          |
| Requested fiat amount   | `amounts.requested.inRequestedCurrency.amount`        | The amount the merchant requested |
| Requested fiat currency | `amounts.requested.inRequestedCurrency.currency.code` | e.g. USD                          |

### When to issue the receipt

Generate or finalize the receipt after the payment reaches a final state. All fields above are available from a single call: `GET /Payments/{id}`. The payment result webhook also includes `conversions[]`, so you can read the exchange rate directly from the webhook payload without a follow-up GET call. See [Payment Statuses](/payments/payment-statuses) for the complete list of final states.

### Receipt delivery

**Integrator-delivered (default):** You deliver the receipt to the consumer using your existing channels — printed, email, SMS, in-app, or portal.

**Bead-delivered (optional):** Enable `emailReceipt` and/or `smsReceipt` on the Create Payment request to have Bead send the receipt directly to the consumer. When enabled, you do not need to send a separate consumer receipt. You remain responsible for merchant-side records, back-office retrievability, and retaining receipt records and delivery evidence for at least 7 years.

### Retention

Retain receipts and evidence of delivery or access for at least 7 years and make them available on request for audit or regulatory review.


# Test Crypto Transactions

While in Bead’s test environment, real crypto tokens must be used. This includes real USDC, BTC for Bitcoin on-chain, and BTC routed over Bitcoin Lightning.

Use this page for guidance on acquiring tokens for test payments, including fee tokens for blockchain or Lightning network fees, and for running an end-to-end test payment through the hosted payment page.

{% hint style="success" %}
**Quick Test - Bitcoin Lightning**

For simple end-to-end testing, we recommend Bitcoin Lightning if enabled for your account. Lightning payments can be as low as $0.01, are near-instant, and have very low fees. This makes Lightning useful for validating payment creation, hosted payment page behavior, payment status updates, and webhooks.
{% endhint %}

For background on how wallets, networks, and fees work, see:

* [Crypto and Wallet Concepts for Integrators](/reference-guide/core-concepts/crypto-and-wallet-concepts-for-integrators)
* [Compatible Crypto Wallets](/reference-guide/operational-guides/compatible-crypto-wallets)
* [Crypto payments, environment and testing](/faqs-and-troubleshooting/environment-and-testing/crypto-payments-environment-and-testing)

{% hint style="warning" %}
**Test Payment Settlement**

Test environment crypto payments are not settled back through normal settlement. Use small amounts when testing.
{% endhint %}

***

{% stepper %}
{% step %}

## Install a Crypto Wallet App

Download a wallet or payment app that supports the tender and network you want to test.

{% hint style="info" %}
**Wallet Availability Varies**

Wallet support, funding methods, minimums, and fees vary by provider and region. Always confirm that your selected wallet supports the asset and network shown on the Bead hosted payment page.
{% endhint %}

### Bitcoin Lightning

Commonly used options include:

1. Cash App for eligible U.S. testers
2. Wallet of Satoshi for non-U.S. testers where available
3. Strike, River, Coinbase, Phoenix, or Muun, depending on region and invoice support

### USDC on Base or Solana

Commonly used options include:

1. Phantom on [Android](https://play.google.com/store/apps/details?id=app.phantom) or [iOS](https://apps.apple.com/us/app/phantom-crypto-wallet/id1598432977)
2. Phantom browser extension for [Chrome](https://chromewebstore.google.com/detail/phantom/bfnaelmomeimhlpmgjnjophhpkkoljpa), [Brave](https://chromewebstore.google.com/detail/phantom/bfnaelmomeimhlpmgjnjophhpkkoljpa), Firefox, or Edge
3. MetaMask, or Trust Wallet for Base and other EVM networks

### Bitcoin On-Chain

Commonly used options include:

1. Phantom on [Android](https://play.google.com/store/apps/details?id=app.phantom) or [iOS](https://apps.apple.com/us/app/phantom-crypto-wallet/id1598432977)
2. Phantom browser extension for [Chrome](https://chromewebstore.google.com/detail/phantom/bfnaelmomeimhlpmgjnjophhpkkoljpa), [Brave](https://chromewebstore.google.com/detail/phantom/bfnaelmomeimhlpmgjnjophhpkkoljpa), Firefox, or Edge
3. Cash App, Strike, River, BlueWallet, or any wallet that can send BTC to an external Bitcoin address

{% endstep %}

{% step %}

## Set Up Your Wallet

Set up your wallet or payment app before starting a test payment.

For self-custody wallets, such as Phantom, MetaMask, Trust Wallet, Phoenix, or Muun:

1. Create a secure password.
2. Record your Secret Recovery Phrase, if one is provided.
3. Store the Secret Recovery Phrase securely.

{% hint style="warning" %}
**Protect Your Recovery Phrase**

If your wallet provides a Secret Recovery Phrase, this is usually the only way to recover your wallet if you move to a new device. If you lose your Secret Recovery Phrase, any funds in your wallet may be lost.
{% endhint %}

{% endstep %}

{% step %}

## Purchase or Add Tokens

Bead does not provide wallet funding, on-ramp, or exchange services. Use the funding options available in your selected wallet, exchange, or payment app.

{% hint style="info" %}
Most wallets and payment apps allow you to purchase or add funds through integrated service providers, subject to regional availability.
{% endhint %}

### Bitcoin Lightning

Some apps allow you to pay a Lightning invoice from a cash balance (e.g. Cash App). Others require BTC to be held in the wallet first.

### USDC on Base or Solana

Acquire USDC on the same network you intend to test:

1. USDC on Base
2. USDC on Solana

You will also need a small amount of the network’s fee token:

1. ETH on Base when testing USDC on Base
2. SOL on Solana when testing USDC on Solana

Base and Solana transactions are typically low cost, but the wallet still needs enough fee token to send the payment. [Converting](#convert-tokens-for-fee-tokens-in-the-phantom-mobile-app) a small amount inside the wallet is often cheaper than buying a small amount directly, depending on the provider.

{% hint style="warning" %}
**Solana Fee Balance**

Solana wallets may require a minimum SOL balance to create or complete a transaction, even when the visible network fee is very small. We recommend you maintain at least $0.40 worth of SOL in the wallet.&#x20;
{% endhint %}

For a Phantom setup example, see [Phantom Wallet Setup Example](#phantom-wallet-setup-example) at the bottom of this page.

### Bitcoin On-Chain

Some apps allow you to create Bitcoin transactions from a cash balance (e.g. Cash App). Others require BTC to be held in the wallet first.

Some apps allow users to purchase BTC directly through integrated service providers. Others require BTC to be transferred into the wallet from an exchange or another wallet before sending.

Bitcoin on-chain fees are paid in BTC. Keep enough BTC in the wallet to cover both the payment amount and the network fee shown before confirmation.

{% endstep %}

{% step %}

## Complete a Test Payment

### Bitcoin Lightning Test Payment

1. Create a payment in Bead’s test environment.
2. From the Bead hosted payment page, select Bitcoin Lightning.
3. Open your Lightning-capable wallet or payment app.
4. Scan the QR code or paste the Lightning invoice.
5. Confirm the amount and fee shown in your wallet.
6. Send the payment.
7. Wait for the hosted payment page and webhook status to update.

### USDC Test Payment

1. Create a payment of at least $1.00 through your test application/API.
2. From the Bead hosted payment page, select USD Coin.
3. Select the network you want to test, such as Base or Solana.
4. Open your wallet and select USDC on the matching network.
5. Scan the QR code or copy the payment address.
6. Enter the payment amount shown on the hosted payment page.
7. Confirm the recipient, network, amount, and fee.
8. Send the payment.

### Bitcoin On-Chain Test Payment

Bitcoin on-chain is supported, but it is not recommended for routine testing because transaction fees and confirmation times can vary.

Use Bitcoin on-chain when you specifically need to validate BTC network behavior.

1. Create a payment in Bead’s test environment.
2. From the Bead hosted payment page, select Bitcoin.
3. Open a Bitcoin wallet that supports on-chain sends.
4. Scan the QR code or copy the Bitcoin address.
5. Confirm the amount, recipient, and network fee.
6. Send the payment.
7. Allow time for network confirmation and status updates.
   {% endstep %}
   {% endstepper %}

***

## Phantom Wallet Setup Example

### Purchase Tokens in the Phantom Mobile App

The following example uses Phantom, focusing on USDC on Base. The same pattern applies to USDC on Solana, with the network set to Solana.

1. Select the "Buy" button from the main app screen.
2. Search for and select your token.
3. For USDC on Base, select USDC on the Base network.
4. Enter your desired purchase amount.
5. USDC has a value of 1:1 with the U.S. Dollar.
6. The minimum transaction size for USDC is $1.00.
7. Purchase enough to cover the test payment amount and any fee-token conversion you need.
8. Select the payment method/provider.
9. Select "Continue" to proceed to the provider quote.
10. Carefully review the network, fees, and exchange rate.
11. Review and complete your payment.
12. Wait for your tokens to arrive.

Always confirm that the token and network shown in your wallet match the network you intend to test before purchasing.

### Convert Tokens (for Fee Tokens) in the Phantom Mobile App

The following example assumes you already have USDC in Phantom.

1. From the home screen, select your existing token.
2. Select "Swap" or "Sell", depending on the Phantom version and context.
3. Enter the amount you wish to convert.
4. Select ETH on Base when testing USDC on Base.
5. Select SOL on Solana when testing USDC on Solana.
6. Review the conversion fee.
7. Confirm the conversion.
8. Wait for your tokens to arrive.

***

## Next Steps and Troubleshooting

If you run into issues while testing crypto payments, use the following pages:

* [Crypto payments, environment and testing](https://developers.bead.xyz/faqs-and-troubleshooting/environment-and-testing/crypto-payments-environment-and-testing)
* [Compatible Crypto Wallets](https://developers.bead.xyz/reference-guide/operational-guides/compatible-crypto-wallets)
* [Payment Webhooks](https://developers.bead.xyz/payments/payment-webhooks)
* [Crypto Testing and Fees](https://developers.bead.xyz/faqs-and-troubleshooting/payments-faqs/crypto-testing-and-fees)


# Testnet Crypto Transactions

While in Bead's test environment, testnet coins must be used. For example, to test a Bitcoin payment, Testnet BTC must be used. Please use the following sections for instructions on how to acquire assets for test payments and how to set up any dependencies.

## BTC - Lightning Network Testnet

{% hint style="danger" %}
Bitcoin Lightning transactions are not available for testing in our Test environment at this time.
{% endhint %}

{% hint style="info" %}
We recommend [Bitcoin Lightning](#bitcoin-lightning) for general testing due to its near-instant transaction times and the ease to acquire Testnet coins.
{% endhint %}

### Set Up Lightning Network Testnet Wallet

1. Go to [**htlc.me**](https://htlc.me)
2. Record the recovery secret and select "**Got it, I wrote it down**"

### Make a Lightning Network Testnet Payment

1. Select "**Bitcoin Lightning**" as the payment option in your payment flow
2. Copy the Payment Request or QR code value
3. Paste the Payment Request or QR code value into the field on the **Send** tab of [**htlc.me**](https://htlc.me)
4. Select "**Send Payment**"

Payment should complete within a few seconds.

***

## BTC - Bitcoin Testnet

### Set Up Bitcoin Testnet Wallet

1. Download a Bitcoin Testnet wallet (e.g. Green: Bitcoin Wallet on [Android](https://play.google.com/store/apps/details?id=com.greenaddress.greenbits_android_wallet))
2. Go to a Bitcoin Testnet Faucet (e.g. [coinfaucet.eu/btc-testnet/](https://coinfaucet.eu/en/btc-testnet/))
3. Request Testnet coins to your mobile Bitcoin Testnet Wallet address
4. Allow some time for the transaction to confirm to receive your Testnet Bitcoin

{% hint style="info" %}
If using the [Green: Bitcoin Wallet](https://play.google.com/store/apps/details?id=com.greenaddress.greenbits_android_wallet), be sure to "Enable Testnet" in the App Settings (lower right corner) before creating a new wallet.
{% endhint %}

### Make a Bitcoin Testnet Payment

1. Select Bitcoin (BTC) as the payment option on your test payment
2. Open your testnet wallet mobile app
3. Select "**Send**" and click the Scan/QR button
4. Scan the QR code on the test payment page\
   (alternatively, copy/paste the "BTC Address" from the payment page)
5. Enter the payment amount as displayed on your payment page
6. Select **Send**

***

## ETH - Ethereum Sepolia Test Network

1. Download a crypto wallet app with Sepolia Test Network support (e.g. Metamask on [Android](https://play.google.com/store/apps/details?id=io.metamask), [iOS](https://apps.apple.com/us/app/metamask-blockchain-wallet/id1438144202))
2. Enable the Sepolia Test Network in your wallet app (in the Metamask app, select the network at the top of the wallet screen, enable "Show test networks" and select "Sepolia Test Network")
3. Copy your SepoliaETH address (in the Metamask app, tap SepoliaETH on the wallet screen, tap **Receive** and copy the address)
4. Go to a SepoliaETH faucet (e.g.[ Sepolia PoW Faucet](https://sepolia-faucet.pk910.de/))
5. Paste in your SepoliaETH address and request/mine coins

{% hint style="info" %}
The [Sepolia PoW Faucet](https://sepolia-faucet.pk910.de/) is a time-efficient option to acquire SepoliaETH; however, it requires "mining" prior to receiving coins, which will consume system resources.
{% endhint %}

***

## USDC - USD Coin

USDC Coin can be transacted on multiple networks. Please see the following subsections to acquire USDC on supported Testnets within our Test environment.

{% hint style="info" %}
If you have added Sepolia and Amoy networks to the same Metamask account, you can use the same account address to get USDC tokens for both networks from the Circle Testnet Faucet.
{% endhint %}

### USDC - Ethereum Sepolia Testnet

#### Import USDC Token

1. Enable the Sepolia Test Network in your wallet app (in the Metamask app, select the network at the top of the wallet screen, enable "Show test networks" and select "Sepolia Test Network")
2. Select "**Import tokens**"
3. Enter the following Token Address for USDC Sepolia: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238\
   (*Note: Token Symbol and Token Decimal should update to "USDC" and "6" respectively*)
4. Select **Import**

**Get USDC on Sepolia Testnet**

1. Copy your SepoliaETH address (in the Metamask app, tap SepoliaETH on the wallet screen, tap **Receive** and copy the address)
2. Go to the [Circle Testnet Faucet](https://faucet.circle.com/) to request Testnet USDC
3. Select "**Ethereum Sepolia**" as the Network and paste in your SepoliaETH address
4. Select "**Send 10 USDC**"

{% hint style="info" %}
To make a test payment with USDC on this network, ETH will be required to pay Gas fees. See the [previous section](#ethereum-eth-sepolia-test-network) for instructions on how to acquire ETH for this purpose.
{% endhint %}

### USDC - Polygon Amoy Testnet

{% hint style="danger" %}
You will need POL (the Polygon network native token) to acquire USDC using the following steps. If you do not have POL on the Amoy test network, complete [this section](#pol-polygon-amoy-testnet-metamask-setup) first.
{% endhint %}

#### Import USDC Token

1. Ensure the Amoy network is active
2. Select "**Import tokens**"
3. Enter the following Token Address for USDC Polygon Amoy: 0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582\
   (*Note: Token Symbol and Token Decimal should update to "USDC" and "6" respectively*)
4. Select **Import**

#### Get USDC on Polygon Amoy Testnet

1. Copy your Amoy address (*Note: this will be the same for POL and USDC tokens*)
2. Go to the [Circle Testnet Faucet](https://faucet.circle.com/) to request Testnet USDC\
   Select "**Polygon PoS Amoy**" as the Network and paste in your Amoy address
3. Select "**Send 10 USDC**"

### USDC - Solana Devnet

1. Copy your Solana Devnet address (in Phantom, tap **Receive** and copy the Solana Devnet address)
2. Go to the [Circle Testnet Faucet](https://faucet.circle.com/) to request Devnet USDC
3. Select "**Solana Devnet**" as the Network and paste in your Solana Devnet address
4. Click "**Send 10 USDC**"

{% hint style="info" %}
To make a test payment with USDC on this network, Devnet SOL is required to pay network fees. See [this section](#get-sol-solana-devnet) for instructions on how to acquire Devnet SOL for this purpose.
{% endhint %}

***

## POL - Polygon Amoy Testnet (Metamask Setup)

1. Download the Metamask app (Android, iOS) and complete the initial setup
2. Select the network at the top of the wallet screen and select "**Add Network**"
3. Select the "**Custom networks**" tab
4. Fill in the fields as follows and select Add:
   * Network Name: Amoy
   * RPC Url: [https://rpc-amoy.polygon.technology](https://rpc-amoy.polygon.technology/)
   * Chain ID: 80002
   * Symbol: POL
   * Block Explorer URL: <https://amoy.polygonscan.com>

{% hint style="info" %}
Polygon's native token (POL) is required to acquire USDC on the Polygon Amoy network. See the [following section](#get-pol-polygon-pos-amoy) for instructions on how to acquire POL for this purpose.
{% endhint %}

### Get POL on Polygon Amoy Testnet

1. Copy your Amoy address from your wallet app
2. Go to the [Polygon Faucet](https://faucet.polygon.technology/)
3. Select "**Connect Discord to get tokens**" and complete the Discord invite
4. Select "**Polygon PoS (Amoy)**" as the Network and paste in your Amoy address
5. Select **Submit**

{% hint style="info" %}
If you don't have a Discord account, there are other faucet options available online; however, they typically require authentication via a third party provider (e.g. Discord, Github).
{% endhint %}

***

## SOL - Solana Devnet

1. Download a crypto wallet app with Solana Devnet support (e.g. Phantom on [Android](https://play.google.com/store/apps/details?id=app.phantom), [iOS](https://apps.apple.com/us/app/phantom-crypto-wallet/id1598432977))
2. Enable the Solana Devnet in your wallet app (in Phantom, go to **Settings** > **Developer Settings**)
3. Copy your Solana Devnet address from your wallet app

### Get SOL on Solana Devnet

1. Copy your Solana Devnet address from your wallet app
2. Go to the [Solana Faucet](https://faucet.solana.com/)
3. Select "**Devnet**" from the dropdown
4. Enter your Solana Devnet wallet address and select an **Amount** of tokens to receive
5. Select "**Confirm Airdrop**"


# Onboarding

The Onboarding APIs support programmatic merchant application creation, hosted signer completion, status tracking, attachment handling, and onboarding event handling.

Use this section when you want to create applications from your own product, prefill merchant data, send the signer through Bead's hosted onboarding experience, track progress in your own systems, or automate follow-up and operational workflows.

If you only need to onboard a small number of merchants manually, portal-based onboarding may be the simpler path.

#### What the onboarding APIs support

With the onboarding APIs, you can:

* create merchant onboarding applications from your own system
* choose between a full application flow and a short application flow
* prefill merchant, stakeholder, banking, and fee information when needed
* control whether the application is sent to the signer immediately or created in Draft status
* upload supporting documents before signature when using the Draft attachment flow
* route the signer through Bead's hosted onboarding and signing experience
* track application progress and current status
* list and filter applications across a partner hierarchy
* resubmit or follow up on existing applications
* subscribe to webhook events for application lifecycle updates

#### Two onboarding submission patterns

There are two primary ways to initiate onboarding through the API.

**Full application**

Endpoint:

```http
POST /merchant-onboarding/applications
```

The full application flow is best when your platform already has most of the merchant's onboarding data and you want to prefill as much of the application as possible before the signer reviews it.

In the full application flow:

* the request body is submitted in `merchantData`
* merchant identity, business details, stakeholder data, banking details, fee information, and `cryptoEnvironment` are included in `merchantData`
* the signer is identified within `merchantData.stakeholders`
* the stakeholder who should sign is marked with `isApplicationSigner: true`
* `submitImmediately` controls whether the application is sent to the signer immediately or created in Draft status

This flow is a strong fit when you want tighter control over the submitted payload and want the signer to mainly review, confirm, and sign.

**Short application**

Endpoint:

```http
POST /merchant-onboarding/applications-short
```

The short application flow is best when you want a lighter entry point and prefer the hosted onboarding experience to collect most of the remaining merchant information.

A short application includes:

* `merchantName`
* `partnerId`
* `partnerMid`
* `signerFirstName`
* `signerLastName`
* `signerEmail`
* `feeInformation`
* `cryptoEnvironment`
* `submitImmediately`

This flow is a strong fit when you want to minimize onboarding data collection in your own system and get the signer into the hosted flow quickly.

#### Shared onboarding inputs

Both submission patterns rely on these key inputs:

* signer details
* fee information
* `cryptoEnvironment`
* `submitImmediately`

For full applications, signer details are represented through the stakeholder marked with `isApplicationSigner: true`.

For short applications, signer details are sent as top-level fields.

`feeInformation` should be aligned to the merchant's agreed commercial setup before the application is created.

`cryptoEnvironment` should match the environment you are working in, such as `sandbox` for Sandbox testing and `production` for Production onboarding.

`submitImmediately` controls whether the application is sent to the signer immediately or created in Draft status.

| Value   | Behavior                                                                                                   |
| ------- | ---------------------------------------------------------------------------------------------------------- |
| omitted | Bead preserves the existing default behavior and sends the application to the signer immediately.          |
| `true`  | Bead creates the application and immediately starts the signer flow.                                       |
| `false` | Bead creates the application in Draft status so attachments can be uploaded before the signer receives it. |

Use `submitImmediately: true` for the simplest onboarding flow.

Use `submitImmediately: false` when you need to upload supporting documents before sending the application for signature.

#### High-level onboarding flow

A typical onboarding workflow depends on whether you submit the application immediately or create it in Draft status first.

**Immediate-submit flow**

Use this flow when attachments are not required before signature.

1. Create the application.

```http
POST /merchant-onboarding/applications
POST /merchant-onboarding/applications-short
```

Use `submitImmediately: true`, or omit the field if you want the default immediate-submit behavior.

2. Bead sends the application to the signer.

The signer receives the hosted onboarding and signing experience using the signer details provided in the request.

3. The signer reviews and signs.

In the full application flow, the signer reviews a more complete prefilled application.

In the short application flow, the signer supplies more of the required business information directly in the hosted experience.

4. Track progress.

Track application progress by polling Get Status, using webhooks, or both.

5. Continue through review and boarding.

After signature, the application moves through review and downstream boarding.

A typical status path may look like:

```
signing → signed → reviewing → reviewed → boarding → boarded
```

**Draft and attachment flow**

Use this flow when you need to upload documents before the signer receives the application.

1. Create the application with `submitImmediately: false`.

```json
{
  "submitImmediately": false,
  "merchantData": {
    "...": "..."
  }
}
```

2. Upload attachments while the application is in Draft status.

```http
POST /merchant-onboarding/applications/{applicationId}/attachments
```

3. Optionally list or download attachments to verify the package.

```http
GET /merchant-onboarding/applications/{applicationId}/attachments
GET /merchant-onboarding/applications/{applicationId}/attachments/{attachmentId}
```

4. Submit the Draft application for signature.

```http
POST /merchant-onboarding/applications/{applicationId}/submit
```

5. Track progress.

After the Draft application is submitted, track status and webhooks the same way as the immediate-submit flow.

A typical status path may look like:

```
draft → signing → signed → reviewing → reviewed → boarding → boarded
```

#### Application identifiers

Both create endpoints return a response that includes:

* `applicationId`
* `envelopeId`
* `status`

Store `applicationId` and `envelopeId` so you can track status, reconcile activity, submit Draft applications, resend applications, and support follow-up workflows later.

#### Core onboarding endpoints

The core merchant onboarding endpoints are:

```http
POST /merchant-onboarding/applications
POST /merchant-onboarding/applications-short
GET /merchant-onboarding/applications/{applicationId}
GET /Partners/{id}/applications
POST /merchant-onboarding/applications/{applicationId}/submit
PUT /merchant-onboarding/applications/{applicationId}
GET /merchant-onboarding/applications/{applicationId}/history
POST /merchant-onboarding/applications/{applicationId}/attachments
GET /merchant-onboarding/applications/{applicationId}/attachments
GET /merchant-onboarding/applications/{applicationId}/attachments/{attachmentId}
DELETE /merchant-onboarding/applications/{applicationId}/attachments/{attachmentId}
POST /merchant-onboarding/{applicationId}/cancel
POST /merchant-onboarding/{partnerId}/webhook
PUT /merchant-onboarding/{partnerId}/webhook
DELETE /merchant-onboarding/{partnerId}/webhook
```

{% hint style="warning" %} **Deprecated:** `GET /merchant-onboarding/applications` is deprecated. Use `GET /Partners/{id}/applications` instead — see [List Applications](/onboarding/list-applications). {% endhint %}

#### Submit, Draft submit, and Resend

These commands are related but serve different purposes.

| Action                      | Endpoint                                                                                                                   | Use when                                                                                             |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Create and immediately send | `POST /merchant-onboarding/applications` or `POST /merchant-onboarding/applications-short` with `submitImmediately: true`  | You want Bead to create the application and send it to the signer immediately.                       |
| Create Draft                | `POST /merchant-onboarding/applications` or `POST /merchant-onboarding/applications-short` with `submitImmediately: false` | You need the application to stay in Draft status so attachments can be uploaded before signature.    |
| Submit Draft                | `POST /merchant-onboarding/applications/{applicationId}/submit`                                                            | The application is in Draft status and is ready to be sent to the signer for the first time.         |
| Resend existing application | `PUT /merchant-onboarding/applications/{applicationId}`                                                                    | The application already entered the signer-facing flow and needs to be sent back to the same signer. |

#### Relationship to other areas

**Onboarding and Entity Management**

Onboarding captures and reviews merchant data.

After approval and downstream boarding, Entity Management is used to create or maintain merchants, locations, terminals, and related entities based on the approved onboarding outcome.

**Onboarding and Payments, Reporting, and Settlement**

Payments use the merchant, location, and terminal entities that are available after onboarding and downstream setup are complete.

Reporting and Settlement use those same entities for history, reconciliation, and funding visibility.

A common implementation sequence is:

1. Implement Onboarding
2. Implement Entity Management
3. Implement Payments
4. Implement Reporting and Settlement

#### Getting started

Before you build your onboarding integration:

1. Obtain your onboarding API key.
2. Decide whether the full or short application flow is the better fit for your use case.
3. Decide whether your application should be sent immediately or created in Draft status.
4. Review Fee Configuration for Onboarding Applications so your submitted fees match the merchant's commercial agreement.
5. If you need supporting documents before signature, review Application Attachments.
6. Review the onboarding request and response models in the Reference Guide.
7. Decide how your system will track status changes, whether by polling, webhooks, or both.
8. Make sure your integration sets `cryptoEnvironment` correctly for the target environment.
9. Test your first end-to-end workflow in Sandbox.

#### Related pages

[**Submit Application**](/onboarding/submit-application)

How to create full and short onboarding applications.

Includes request structure, signer guidance, `submitImmediately`, response examples, and the difference between immediate-submit and Draft application creation.

[**Fee Configuration for Onboarding Applications**](/onboarding/fee-configuration-for-onboarding-applications)

How to structure settlement, crypto, and digital wallet pricing in onboarding requests.

Use this page before submitting applications so the signer sees the correct fee configuration.

[**Application Attachments**](/onboarding/application-attachments)

How to upload, list, download, and delete supporting documents for Draft applications.

Use this page when creating applications with `submitImmediately: false`.

[**Test the Full Onboarding Workflow in Sandbox**](/onboarding/test-the-full-onboarding-workflow-in-sandbox)

Step-by-step guidance for running an end-to-end Sandbox onboarding test, including signer completion, status tracking, webhook validation, and the Sandbox manual-advance step.

[**Get Status**](/onboarding/get-status)

How to retrieve the current onboarding application state and interpret lifecycle statuses such as `draft`, `signing`, `signed`, `reviewing`, `boarding`, and `boarded`.

[**List Applications**](/onboarding/list-applications)

How to retrieve a paginated, filterable list of onboarding applications for a partner and its descendants.

Use this page when you need to find or monitor applications across a partner hierarchy rather than look up a single known `applicationId`.

[**Resend Application**](/onboarding/resend-application)

How to resend an existing application to the same signer.

Use this page when an application has already entered the signer-facing flow and needs follow-up.

[**Webhooks for Application Events**](/onboarding/webhooks-for-application-events)

How to subscribe to onboarding application events and process them safely.

[**Sample Payload**](/onboarding/sample-payload)

Example request bodies for the short application, full application, Draft application, and common fee configurations.


# Submit Application

## Submit Application

Create a new merchant onboarding application through the API.

Two submission patterns are available:

* a full application with merchant data prefilled in your request
* a short application that starts the onboarding process with a smaller payload and lets the hosted onboarding flow collect the remaining information

By default, Bead creates the application and immediately sends it to the signer. If you need to upload supporting documents before the signer receives the application, set `submitImmediately` to `false` so the application is created in Draft status.

For the full application flow, identify the signer inside `merchantData.stakeholders` by setting `isApplicationSigner` to `true`.

For the short application flow, send signer details using `signerFirstName`, `signerLastName`, and `signerEmail`.

### Choosing a flow

Choose `POST /merchant-onboarding/applications` when:

* your system already has merchant business, ownership, banking, and fee information
* you want to prefill the onboarding package as much as possible
* you want the signer to mainly review, confirm, and sign
* you may need to create the application in Draft status and upload attachments before sending it to the signer

Choose `POST /merchant-onboarding/applications-short` when:

* you want a lighter-weight entry point
* you want the hosted onboarding flow to collect most of the remaining merchant information
* you still want to provide signer details, fee information, and crypto environment up front

For both flows:

* provide the required fee configuration
* set `cryptoEnvironment` correctly for the environment you are using
* decide whether to submit immediately or create the application in Draft status
* store the returned `applicationId` and `envelopeId`

### Authentication and headers

Onboarding requests use API key authentication.

### Submit behavior

Use `submitImmediately` to control whether Bead sends the application to the signer immediately or creates it in Draft status.

| Value   | Behavior                                                                                                   |
| ------- | ---------------------------------------------------------------------------------------------------------- |
| omitted | Bead preserves the existing default behavior and sends the application to the signer immediately.          |
| `true`  | Bead creates the application and immediately starts the signer flow.                                       |
| `false` | Bead creates the application in Draft status so attachments can be uploaded before the signer receives it. |

Use `submitImmediately: false` when you need to upload supporting documents through the attachment endpoints before sending the application for signature.

After all required attachments have been uploaded, call:

```
POST /merchant-onboarding/applications/{applicationId}/submit
```

This submits the Draft application for signature and starts the DocuSign signing flow. The latest OpenAPI includes this dedicated Draft submit endpoint, separate from the resend/resubmit endpoint.

### Full application

The full application flow is designed for partners that want to submit a more complete onboarding request from their own system.

```
POST /merchant-onboarding/applications
```

#### Signer guidance

For the full application flow, the signer is identified in `merchantData.stakeholders`.

Set `isApplicationSigner` to `true` for the stakeholder who should receive and sign the application.

Only one stakeholder should be designated as the application signer.

#### What to include

A full application typically includes:

* merchant identity and business details in `merchantData`
* contact and point-of-contact information
* stakeholder and ownership details
* banking and settlement details
* fee configuration
* crypto environment
* `submitImmediately` when you want to explicitly control Draft vs. immediate-send behavior

> **Set `locationBusinessType` carefully.** This field determines which payment connections are provisioned for the merchant at boarding time. Setting it to `"physical"` means virtual wallet connections (Klarna, USDC, Lightning, etc.) will not be built — Virtual terminals using those tenders will fail to create. If the merchant needs to accept virtual wallet payments, use `"virtual"` or `"both"`. This value cannot be changed after boarding without a full application resubmission. See [Location Management](/entity-management/location-management) for the full value breakdown and impact table.

#### Example: submit immediately

This example shows the default one-step flow. Bead creates the application and sends it to the signer.

You can omit `submitImmediately` when you want the default immediate-send behavior.

#### Example: create Draft application for attachments

Use this pattern when you need to upload documents before the signer receives the application.

After creating the Draft application, upload attachments while the application is still in Draft status.

Then submit the Draft application for signature:

```
POST /merchant-onboarding/applications/{applicationId}/submit
```

Attachments can only be added while the application is in Draft status, and deleting attachments is also limited to Draft applications.

### Short application

The short application flow is designed for partners that want to start onboarding quickly and let the hosted onboarding flow collect most of the remaining merchant information.

```
POST /merchant-onboarding/applications-short
```

#### Signer guidance

For the short application flow, send signer details as top-level fields:

* `signerFirstName`
* `signerLastName`
* `signerEmail`

#### What to include

A short application typically includes:

* `merchantName`
* `partnerId`
* `partnerMid`
* `signerFirstName`, `signerLastName`, and `signerEmail`
* `feeInformation`
* `cryptoEnvironment`
* `submitImmediately`

> **Set `locationBusinessType` carefully.** This field determines which payment connections are provisioned for the merchant at boarding time. Setting it to `"physical"` means virtual wallet connections (Klarna, USDC, Lightning, etc.) will not be built — Virtual terminals using those tenders will fail to create. If the merchant needs to accept virtual wallet payments, use `"virtual"` or `"both"`. This value cannot be changed after boarding without a full application resubmission. See [Location Management](/entity-management/location-management) for the full value breakdown and impact table.

### What happens next

If `submitImmediately` is omitted or set to `true`:

1. Bead creates the onboarding application record.
2. Bead creates the related signing package.
3. Bead sends the package to the signer.
4. The signer completes the hosted onboarding flow.
5. You track progress using the status endpoints and application event webhooks.

If `submitImmediately` is set to `false`:

1. Bead creates the onboarding application record in Draft status.
2. You upload any required attachments.
3. You optionally list or download attachments to verify the package.
4. You submit the Draft application using `POST /merchant-onboarding/applications/{applicationId}/submit`.
5. Bead sends the package to the signer.
6. You track progress using the status endpoints and application event webhooks.

### Typical response fields

A successful response includes:

* `applicationId`
* `envelopeId`
* `status`

Store `applicationId` and `envelopeId` so you can track, submit, resend, or troubleshoot the application later.

For applications submitted immediately, the returned status may reflect the sent or signing state.

### Best practices

* Choose the full application flow when you already have reliable onboarding data.
* Choose the short application flow when you want the hosted onboarding experience to collect most of the remaining data.
* For the full application flow, mark one stakeholder in `merchantData.stakeholders` with `isApplicationSigner: true`.
* **Set `locationBusinessType` to `"virtual"` or `"both"` if the merchant needs to accept virtual wallet payments.** A `"physical"`-only boarding cannot be changed post-boarding without resubmission.
* Use `submitImmediately: false` when you need to upload attachments before the signer receives the application.
* Upload and verify all required attachments before calling `POST /merchant-onboarding/applications/{applicationId}/submit`.
* Keep fee configuration aligned with your commercial agreement before submitting the application.
* Store the returned identifiers so you can support submit, resend, status, webhook, and support workflows.
* Use international phone formatting such as `+15085550100` where applicable.

### Related pages

* [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications)
* [Application Attachments](/onboarding/application-attachments)
* [Get Status](/onboarding/get-status)
* [Resend Application](/onboarding/resend-application)
* [Webhooks for Application Events](/onboarding/webhooks-for-application-events)
* [Sample Payload](/onboarding/sample-payload)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)


# Fee Configuration for Onboarding Applications

Provide `feeInformation` when creating onboarding applications that will be sent to a signer.

Fees are part of merchant setup. They are submitted through the onboarding APIs, shown in the signing experience, and should be treated as fixed for the application rather than something the signer edits during the hosted flow.

If fee data is incomplete or missing, the application may not board as intended, and wallet tenders may not be made available for the merchant.

#### Where `feeInformation` goes

Placement depends on the onboarding flow.

**Full application**

For the full application flow, include `feeInformation` inside `merchantData`.

**Short application**

For the short application flow, include `feeInformation` at the top level of the request body.

#### How fee configuration works

All merchant fees for onboarding come through the application APIs.

That includes:

* settlement-related fixed fees
* crypto fees
* digital wallet fees

These values should be included when the application is created. They are not intended to be negotiated, corrected, or completed later by the signer inside the hosted onboarding flow.

#### Required fee groups

A typical `feeInformation` object includes three groups of fees:

* settlement fixed fees
* crypto fees
* digital wallet fees

**Settlement fixed fees**

Settlement fixed fees apply at the merchant level.

Typical fields include:

* `achSettlementFixedFee`
* `settlementReturnFixedFee`
* `monthlyMaintenanceFee`

These should be provided as part of the onboarding request so the merchant is boarded with the agreed commercial setup.

**Crypto fees**

Crypto fees are configured using `bitcoinFee` and `stablecoinFee`. Each takes a `transactionRate` and `perItemFee`. Both fields are optional — include only the ones applicable to the merchant's agreement.

Do not use `cryptoFees` when submitting an application. That field appears in read responses only and will not be accepted on submission.

**Digital wallet fees**

Digital wallets are configured differently from crypto.

Digital wallet fees are defined as wallet-specific entries under `digitalWalletsFees`.

Each entry is tied to:

* a wallet tender type
* a payment location type

This allows wallet pricing to vary by tender and by channel, such as:

* `inStore`
* `online`

A merchant may have fee entries for one or many supported digital wallet tenders. Common examples include:

* `alipay`
* `paypal`
* `venmo`
* `weChatPay`
* `klarna`

If a merchant is expected to support a wallet in more than one channel, provide a separate fee entry for each supported channel.

If a digital wallet fee is not present on the application, that digital wallet will not be available for the merchant.

#### How wallet availability works

Digital wallet availability depends on the fee entries included on the application.

If a digital wallet fee is not present on the application, that digital wallet will not be available for the merchant.

This is an important setup rule. If a merchant is expected to board with a specific wallet tender, include that wallet's agreed fee entry at application creation time.

#### Best practice

It is best practice to provide all agreed fees while collecting and submitting the merchant application.

That includes:

* the settlement fees
* the agreed crypto fee structure
* all intended digital wallet fee entries

This is the safest pattern because the merchant can always have tenders disabled later at the merchant, location, or terminal level if needed.

It is generally better to board with the agreed fee-backed tenders configured up front than to omit them during onboarding and try to correct tender availability later.

#### Recommended implementation approach

When building your onboarding integration:

1. determine the merchant's agreed commercial setup before creating the application
2. build `feeInformation` from that agreement
3. include all intended settlement, crypto, and wallet fees
4. submit the application only after fee configuration is complete
5. treat missing wallet fee entries as a configuration issue, not as something the signer will fix later

#### Common mistakes

**Sending only settlement fees**

If the merchant is expected to board for crypto or digital wallets, settlement fees alone are not enough.

**Using `cryptoFees` on submission**

`cryptoFees` is a read-only response field. When submitting an application, use `bitcoinFee` and `stablecoinFee` instead. Sending `cryptoFees` on submission will result in crypto fees not being applied, which causes boarding to fail.

**Omitting wallet fee entries**

If a wallet fee entry is missing from the application, that wallet should not be expected to board for the merchant.

**Waiting to add agreed fees later**

This increases the chance of incomplete merchant setup. It is better to send the agreed fees up front and disable tenders later if needed.

#### Example `feeInformation` structure

{% code expandable="true" %}

```json
{
  "feeInformation": {
    "achSettlementFixedFee": {
      "sellRate": 1,
      "isBilledByPartner": false
    },
    "settlementReturnFixedFee": {
      "sellRate": 25,
      "isBilledByPartner": false
    },
    "monthlyMaintenanceFee": {
      "sellRate": 19.99,
      "isBilledByPartner": false
    },
    "bitcoinFee": {
      "transactionRate": {
        "sellRate": 2.5,
        "isBilledByPartner": false
      },
      "perItemFee": {
        "sellRate": 0.25,
        "isBilledByPartner": false
      }
    },
    "stablecoinFee": {
      "transactionRate": {
        "sellRate": 2.5,
        "isBilledByPartner": false
      },
      "perItemFee": {
        "sellRate": 0.25,
        "isBilledByPartner": false
      }
    },
    "digitalWalletsFees": [
      {
        "tenderType": "alipay",
        "paymentLocationType": "inStore",
        "transactionRate": {
          "sellRate": 3.1,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.3,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "alipay",
        "paymentLocationType": "online",
        "transactionRate": {
          "sellRate": 3.1,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.3,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "paypal",
        "paymentLocationType": "inStore",
        "transactionRate": {
          "sellRate": 2.29,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.09,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "paypal",
        "paymentLocationType": "online",
        "transactionRate": {
          "sellRate": 3.6,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.49,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "venmo",
        "paymentLocationType": "inStore",
        "transactionRate": {
          "sellRate": 2.29,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.09,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "venmo",
        "paymentLocationType": "online",
        "transactionRate": {
          "sellRate": 3.6,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.49,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "weChatPay",
        "paymentLocationType": "inStore",
        "transactionRate": {
          "sellRate": 3.1,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.3,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "weChatPay",
        "paymentLocationType": "online",
        "transactionRate": {
          "sellRate": 3.1,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.3,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "klarna",
        "paymentLocationType": "inStore",
        "transactionRate": {
          "sellRate": 5.79,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.45,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "klarna",
        "paymentLocationType": "online",
        "transactionRate": {
          "sellRate": 5.79,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.45,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      }
    ]
  }
}
```

{% endcode %}

#### Related pages

* [Submit Application](/onboarding/submit-application)
* [Sample Payload](/onboarding/sample-payload)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)


# Application Attachments

Use application attachments to upload supporting documents to a merchant onboarding application before the signer receives the application.

Attachments are only available for applications in Draft status. To create an application in Draft status, set `submitImmediately` to `false` when you create the application.

After all required attachments are uploaded, submit the Draft application for signature.

```http
POST /merchant-onboarding/applications/{applicationId}/submit
```

### When to use attachments

Use attachments when you need to provide supporting documents before the application is sent to the signer.

Common examples include:

* proof of business
* proof of legal or business address
* proof of bank account
* stakeholder proof of identity
* stakeholder proof of address
* a Cash App logo, when applicable
* other supporting documentation requested during onboarding

Attachments are most useful when your system already collects documents from the merchant and you want to include those files with the onboarding package before signature.

### Attachment workflow

The attachment workflow has four steps.

1. Create the application in Draft status.

```json
{
  "submitImmediately": false,
  "merchantData": {
    "...": "..."
  }
}
```

2. Upload one or more attachments.

```http
POST /merchant-onboarding/applications/{applicationId}/attachments
```

3. Optionally list or download attachments to confirm the package is complete.

```http
GET /merchant-onboarding/applications/{applicationId}/attachments
GET /merchant-onboarding/applications/{applicationId}/attachments/{attachmentId}
```

4. Submit the Draft application for signature.

```http
POST /merchant-onboarding/applications/{applicationId}/submit
```

After the application leaves Draft status, attachments can no longer be added or deleted.

### Create the application in Draft status

Set `submitImmediately` to `false` when creating the application.

```http
POST /merchant-onboarding/applications
```

{% code expandable="true" %}

```json
{
  "submitImmediately": false,
  "merchantData": {
    "merchantName": "Northwind Coffee Roasters",
    "partnerId": "partner_123",
    "partnerExternalId": "ext-merchant-2048",
    "registeredName": "Northwind Coffee Roasters LLC",
    "dbaName": "Northwind Coffee Roasters",
    "merchantCategoryCode": "5499",
    "cryptoEnvironment": "sandbox",
    "stakeholders": [
      {
        "firstName": "Morgan",
        "lastName": "Reed",
        "email": "morgan.reed@example.com",
        "phoneNumber": "+15085550101",
        "jobTitle": "Owner",
        "ownershipPercentage": 100,
        "ownershipType": "direct",
        "dateOfBirth": "1987-06-15",
        "citizenship": "US",
        "idType": "passport",
        "idNumber": "123456789",
        "countryOfIssuance": "US",
        "taxIdType": "ssn",
        "taxIdNumber": "123456789",
        "isApplicationSigner": true,
        "proofOfIdentityType": "passport",
        "proofOfAddressType": "utilityBill",
        "address": {
          "address1": "123 Harbor Street",
          "city": "Boston",
          "region": "MA",
          "country": "US",
          "postalCode": "02110"
        }        
      }
    ],
    "feeInformation": {
      "achSettlementFixedFee": {
        "sellRate": 1.5,
        "isBilledByPartner": false
      },
      "settlementReturnFixedFee": {
        "sellRate": 15,
        "isBilledByPartner": false
      },
      "monthlyMaintenanceFee": {
        "sellRate": 0,
        "isBilledByPartner": false
      }
    }
  }
}
```

{% endcode %}

A successful Draft response includes the `applicationId`. Use that value for the attachment endpoints.

```json
{
  "applicationId": "app_123",
  "envelopeId": "env_456",
  "status": "draft"
}
```

### Upload an attachment

Upload an attachment to a Draft application.

```http
POST /merchant-onboarding/applications/{applicationId}/attachments
```

Each upload includes:

| Field              | Required    | Description                                                     |
| ------------------ | ----------- | --------------------------------------------------------------- |
| `file`             | Yes         | The PDF, JPEG, or PNG document to upload.                       |
| `category`         | Yes         | The document category that identifies what the file represents. |
| `stakeholderIndex` | Conditional | Required for stakeholder-specific document categories.          |
| `type`             | Optional    | Optional descriptor when additional context is needed.          |

#### Example upload

```bash
curl --request POST \
  --url https://api.test.devs.beadpay.io/merchant-onboarding/applications/app_123/attachments \
  --header "X-Api-Key: {apiKey}" \
  --form "file=@proof-of-bank-account.pdf" \
  --form "category=proofOfBankAccount"
```

#### Example stakeholder upload

Use `stakeholderIndex` when uploading a stakeholder document.

```bash
curl --request POST \
  --url https://api.test.devs.beadpay.io/merchant-onboarding/applications/app_123/attachments \
  --header "X-Api-Key: {apiKey}" \
  --form "file=@owner-driver-license-front.png" \
  --form "category=stakeholderProofOfIdentityFront" \
  --form "stakeholderIndex=0"
```

`stakeholderIndex` is zero-based and maps to the order of `merchantData.stakeholders`.

For example:

* `stakeholderIndex: 0` maps to the first stakeholder in `merchantData.stakeholders`
* `stakeholderIndex: 1` maps to the second stakeholder in `merchantData.stakeholders`

### Attachment categories

Use the category that best describes the uploaded document.

| Category                          | Use for                                                      |
| --------------------------------- | ------------------------------------------------------------ |
| `proofOfBusiness`                 | Business formation or registration documentation.            |
| `legalProofOfAddress`             | Proof for the merchant’s legal address.                      |
| `businessProofOfAddress`          | Proof for the merchant’s business or operating address.      |
| `proofOfBankAccount`              | Bank account proof such as a voided check or bank letter.    |
| `cashAppLogo`                     | Cash App logo documentation, when applicable.                |
| `stakeholderProofOfIdentityFront` | Front image of a stakeholder identity document.              |
| `stakeholderProofOfIdentityBack`  | Back image of a stakeholder identity document.               |
| `stakeholderProofOfAddress`       | Stakeholder proof of address.                                |
| `other`                           | Supporting documentation that does not fit another category. |

Use stakeholder categories only for documents tied to a specific stakeholder. Include `stakeholderIndex` so Bead can associate the attachment with the correct person.

### Attachment metadata

The upload response returns metadata about the attachment.

```json
{
  "id": "att_123",
  "fileName": "proof-of-bank-account.pdf",
  "contentType": "application/pdf",
  "documentCategory": "proofOfBankAccount",
  "stakeholderIndex": null,
  "sizeBytes": 245912,
  "uploadedAt": "2026-05-06T18:30:00Z",
  "uploadedBy": "partner-api"
}
```

Store the returned `id` if you need to download or delete the attachment later.

### List attachments

List all attachments for an application.

```http
GET /merchant-onboarding/applications/{applicationId}/attachments
```

Example response:

```json
[
  {
    "id": "att_123",
    "fileName": "proof-of-bank-account.pdf",
    "contentType": "application/pdf",
    "documentCategory": "proofOfBankAccount",
    "stakeholderIndex": null,
    "sizeBytes": 245912,
    "uploadedAt": "2026-05-06T18:30:00Z",
    "uploadedBy": "partner-api"
  },
  {
    "id": "att_456",
    "fileName": "owner-driver-license-front.png",
    "contentType": "image/png",
    "documentCategory": "stakeholderProofOfIdentityFront",
    "stakeholderIndex": 0,
    "sizeBytes": 385102,
    "uploadedAt": "2026-05-06T18:31:00Z",
    "uploadedBy": "partner-api"
  }
]
```

Use this endpoint to verify that all required documents were uploaded before submitting the Draft application for signature.

### Download an attachment

Download a single attachment.

```http
GET /merchant-onboarding/applications/{applicationId}/attachments/{attachmentId}
```

Use this endpoint when you need to confirm the uploaded file or support a troubleshooting workflow.

### Delete an attachment

Delete an attachment from a Draft application.

```http
DELETE /merchant-onboarding/applications/{applicationId}/attachments/{attachmentId}
```

A successful delete returns:

```http
204 No Content
```

Deleting attachments is only allowed while the application is still in Draft status. After the application is submitted for signature, attachments cannot be deleted.

### Replacing an attachment

There is no separate update attachment command.

To replace an attachment while the application is still in Draft status:

1. Delete the existing attachment.
2. Upload the replacement file.
3. List attachments to confirm the replacement was added.

```http
DELETE /merchant-onboarding/applications/{applicationId}/attachments/{attachmentId}
POST /merchant-onboarding/applications/{applicationId}/attachments
GET /merchant-onboarding/applications/{applicationId}/attachments
```

### Submit the Draft application

After all required attachments are uploaded, submit the Draft application for signature.

```http
POST /merchant-onboarding/applications/{applicationId}/submit
```

This sends the application to the signer and starts the signing flow.

After this step, the application is no longer in Draft status and attachments can no longer be added or deleted.

### Error handling

| Scenario                                                    | Expected result                                                                                                                              |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Uploading an attachment to a non-Draft application          | The request fails because attachments can only be added in Draft status.                                                                     |
| Deleting an attachment after submission                     | The request fails because attachments can only be deleted in Draft status.                                                                   |
| Uploading without `category`                                | The request fails validation.                                                                                                                |
| Uploading a stakeholder document without `stakeholderIndex` | The request may fail validation or may not associate the document correctly. Include `stakeholderIndex` for stakeholder-specific categories. |
| Uploading an unsupported file type                          | The request fails validation.                                                                                                                |
| Downloading an unknown `attachmentId`                       | The request returns not found.                                                                                                               |

### Best practices

* Create the application with `submitImmediately: false` whenever attachments are required.
* Upload all required documents before submitting the Draft application for signature.
* Use the most specific attachment category available.
* Use `stakeholderIndex` for stakeholder proof documents.
* List attachments after upload to confirm the package is complete.
* Delete and re-upload an attachment if a file needs to be replaced before submission.
* Submit the Draft application only after the attachment set is complete.
* Store attachment IDs for support and troubleshooting.
* Do not use the `other` category when a more specific category applies.

### Related pages

* [Submit Application](/onboarding/submit-application)
* [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)
* [Get Status](/onboarding/get-status)
* [Resend Application](/onboarding/resend-application)
* [Webhooks for Application Events](/onboarding/webhooks-for-application-events)
* [Sample Payload](/onboarding/sample-payload)


# Field Validation Rules

Before a submitted merchant onboarding application reaches a Bead reviewer, it is run through a set of automated field-level checks. These are the same checks Bead's own review tooling runs internally — validating your payload against them before submission catches the most common causes of review delay before they ever reach us.

This page documents every rule currently enforced, grouped by category. Each rule lists what it checks, why it exists, and how to fix a failure.

> **Tip:** Most failures on this list are cheap to catch client-side with a regex or a lookup table. If you're building an integration against the Onboarding API, we recommend running these checks against your payload before calling `POST /merchant-onboarding/applications` or `POST /merchant-onboarding/applications-short`.

#### How to read this page

Each rule below maps to a specific field or field pattern in the [Merchant Onboarding Schema](/reference-guide/enumerations-and-schemas/merchant-onboarding-schema). Fields are referenced using their path in the request payload, for example `merchantData.stakeholders[].phoneNumber`.

### Phone numbers

**Phone number can normalize to E.164**

Every phone number field must be cleanly convertible to E.164 format (`+15551234567`).

* **Applies to:** `mainPointOfContact.phoneNumber`, `stakeholders[].phoneNumber`, `businessPhone`, `signerPhone` (short application)
* **Fix:** Strip formatting characters and confirm the number resolves to a valid `+`-prefixed E.164 string before sending.

**Phone number can convert from E.164 to E.123**

Every phone number field must convert from its normalized E.164 form into the downstream E.123 international format (`+1 480 332 1255`).

* **Applies to:** Same fields as above.
* **Fix:** This is a downstream consequence of the E.164 check — if a number passes E.164 normalization but fails E.123 conversion, it usually indicates an unusual or malformed national number. Re-verify the number with the merchant.

**Phone number includes US country code**

Phone numbers should include an explicit US country code (`+1`).

* **Applies to:** Same fields as above.
* **Fix:** Always send the leading `+1` rather than a bare 10-digit number.

### Contact formatting

**Email is properly formatted**

Every email field must be a valid, well-formed email address.

* **Applies to:** `mainPointOfContact.email`, `stakeholders[].email`, `businessEmail`, `signerEmail`
* **Fix:** Validate against a standard email regex or library before submission. Watch for trailing whitespace and copy-paste artifacts from CRM exports.

**Website is properly formatted**

Every website field must be a valid `http` or `https` URL.

* **Applies to:** `businessWebsite`
* **Fix:** Include the protocol (`https://…`). A bare domain without a scheme will fail.

### Identity and compliance

**Date of birth is 10+ years old**

Every stakeholder date of birth must be at least 10 years before today.

* **Applies to:** `stakeholders[].dateOfBirth`
* **Fix:** Confirm the date isn't transposed (day/month swap is the most common cause) and that it isn't accidentally defaulting to the current date.

**SSN uses valid US ranges**

An SSN used as a tax ID must be 9 digits, with:

* area number not `000`, `666`, or in the `900`–`999` range
* group number not `00`
* serial number not `0000`
* **Applies to:** `stakeholders[].taxIdNumber` when `taxIdType` is `ssn`
* **Fix:** These ranges were never issued by the SSA and indicate placeholder, test, or transposed data. Confirm the value with the stakeholder rather than guessing a fix. Remember the field must also be exactly 9 digits with no dashes — see [Merchant Onboarding Schema](/reference-guide/enumerations-and-schemas/merchant-onboarding-schema).

**Business EIN differs from stakeholder tax ID**

The business `federalTaxId` (EIN) must not match any stakeholder's `taxIdNumber`.

* **Applies to:** `federalTaxId` compared against every `stakeholders[].taxIdNumber`
* **Fix:** This usually indicates the business EIN was copy-pasted into a stakeholder record, or vice versa. Confirm each identifier was pulled from the correct source field.

### Address

**Street 1 or address 1 is populated**

Every address object must include `address1` (or the equivalent street line) with more than 5 characters.

* **Applies to:** `registeredLegalAddress`, `businessMailingAddress`, `stakeholders[].address`, `bankAddress`
* **Fix:** A PO Box abbreviation or single-character placeholder will fail this check. Confirm a real street line is present.

**Postal code has 5+ digits**

Every address object must include a postal or zip code with at least 5 digits.

* **Applies to:** Same address objects as above.
* **Fix:** Confirm the field isn't truncated (e.g., a leading zero dropped by a spreadsheet import).

**State is 2 characters**

Every address object must include a 2-letter state or region code.

* **Applies to:** `region` field on the same address objects.
* **Fix:** Send USPS 2-letter abbreviations (`MA`, not `Massachusetts`).

**Country is US only**

Every address object must have `country` set to `US`.

* **Applies to:** Same address objects as above.
* **Fix:** Bead onboarding currently supports US-domiciled merchants only. Confirm `country` is the literal string `US`, not a full country name.

**Citizenship and issuance country are US**

Every stakeholder `citizenship` and `countryOfIssuance` field must be set to `US`.

* **Applies to:** `stakeholders[].citizenship`, `stakeholders[].countryOfIssuance`
* **Fix:** As with address country, this must be the ISO alpha-2 code `US`.

### Classification codes

**NAICS is a current 2022 code**

`naicsCode` must be a current 2022, 6-digit NAICS code.

* **Applies to:** `naicsCode`
* **Fix:** Legacy 2- or 4-digit NAICS codes (from older classification revisions) will fail. Look up the current 6-digit 2022 code for the merchant's business activity — the [U.S. Census NAICS lookup tool](https://www.census.gov/naics/) is the canonical source. This is the single most common validation failure seen in review.

**MCC is 4 digits**

`merchantCategoryCode` must be a valid 4-digit number and cannot be all zeros or all nines.

* **Applies to:** `merchantCategoryCode`, `additionalMerchantCategoryCode`
* **Fix:** Send as a string to preserve leading zeros (see [Common Field Types](/reference-guide/enumerations-and-schemas/common-field-types)), and confirm the value isn't a placeholder like `0000` or `9999`.

### Attachments

**Attachments are PDF only**

All application attachments must be PDF files.

* **Applies to:** Files uploaded through the [Application Attachments](/onboarding/application-attachments) endpoints.
* **Fix:** Convert scanned images or Office documents to PDF before upload. Other formats will be rejected at upload time or flagged in review.

### Pricing and tenders

**Tender pricing has both percentage and transaction rates**

Bitcoin and USDC pricing must be present, and each configured tender must include both a percentage fee rate and a transaction fee rate.

* **Applies to:** `feeInformation.bitcoinFee`, `feeInformation.stablecoinFee`
* **Fix:** Both `transactionRate` and `perItemFee` sub-objects are required for each configured tender — confirm neither was omitted when building `feeInformation`. See [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications).

**Partner-enabled wallets have pricing configured**

Any wallet tender enabled at the partner level must have a corresponding entry in `feeInformation.digitalWalletsFees`. This is a warning-level check, not a hard failure.

* **Applies to:** `feeInformation.digitalWalletsFees`
* **Fix:** Cross-check the merchant's enabled wallet tenders against your partner-level tender configuration and add any missing pricing entries.

### Settlement-only applications

**Settlement-only applications omit disallowed revenue and ticket fields**

When `isSettlementOnly` is `true`, the application must **not** include `grossAnnualRevenue`, `highestMonthlyVolume`, `averageTicketSize`, or `maxTicketSize`.

* **Applies to:** `grossAnnualRevenue`, `highestMonthlyVolume`, `averageTicketSize`, `maxTicketSize` when `isSettlementOnly` is `true`
* **Fix:** Omit these fields entirely from the payload for settlement-only merchants rather than sending zero or null values.

### Related pages

* [Submit Application](/onboarding/submit-application)
* [Merchant Onboarding Schema](/reference-guide/enumerations-and-schemas/merchant-onboarding-schema)
* [Common Field Types](/reference-guide/enumerations-and-schemas/common-field-types)
* [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications)
* [Application Attachments](/onboarding/application-attachments)
* [How do I resolve a 400 ValidationError?](/faqs-and-troubleshooting/webhooks-and-error-codes/how-do-i-resolve-a-400-validationerror)


# Test the Full Onboarding Workflow in Sandbox

Use this page to validate the full merchant onboarding workflow in Sandbox, from application creation through signer completion, status tracking, webhook validation, and final boarded merchant confirmation.

This guide uses the immediate-submit path:

```json
{
  "submitImmediately": true
}
```

When `submitImmediately` is `true`, Bead creates the application and immediately starts the signer flow. This is the easiest path for a first full workflow test because you do not need to manage Draft status or attachments.

This guide applies to both:

```http
POST /merchant-onboarding/applications
POST /merchant-onboarding/applications-short
```

For most teams, the fastest Sandbox test path is the short application flow, because it lets the hosted onboarding experience collect most of the remaining merchant information.

This page intentionally focuses on the workflow. For exact request schemas and full payload examples, use Submit Application, Fee Configuration for Onboarding Applications, and Sample Payload.

#### What this test should prove

A successful end-to-end Sandbox test should confirm that your integration can:

* create an onboarding application
* trigger the signer flow using `submitImmediately: true`
* send the signer into the hosted onboarding experience
* complete all required signer steps
* track application progress by polling status, receiving webhooks, or both
* handle the Sandbox-specific manual advance step
* confirm that the merchant reaches a boarded state

#### Before you begin

Before running this test:

* use a valid, deliverable email address that you can access
* use a unique signer email address for each test application — see [Test data: email address requirements](/faqs-and-troubleshooting/environment-and-testing/test-data-uniqueness-requirements) for recommended patterns
* use a distinct registered business name for each test application — the sponsor bank enforces business name uniqueness even in Sandbox; reusing the same business name across applications can cause rejections
* use a varied signer name for each test application — reusing the same signer name across many applications can trigger duplicate-identity checks at the boarding layer
* use unique merchant identifiers, such as `partnerExternalId` or `partnerMid`
* set `cryptoEnvironment` to `sandbox`
* set `submitImmediately` to `true`
* store the returned `applicationId` and `envelopeId`
* decide whether you will track progress through polling, webhooks, or both
* if you plan to test webhooks, configure your Sandbox webhook endpoint before submitting the application

For true end-to-end testing, let Bead deliver the signer email through the standard flow. This validates the same email-driven path a merchant signer will use.

#### Test data uniqueness requirements

The sponsor bank applies uniqueness checks to several fields at the boarding layer. These checks apply even in Sandbox. Reusing the same values across test applications is the most common source of unexpected rejections during integration testing.

**Signer email**

Use a unique email address for every test application. The recommended approach is plus-addressing with a timestamp:

```
qa+20260127143015@yourcompany.com
```

See [Test data: email address requirements](/faqs-and-troubleshooting/environment-and-testing/test-data-uniqueness-requirements) for full patterns, including handling for parallel test runs.

**Business name**

Use a distinct registered business name for every test application. A simple approach is to append a numeric suffix or short identifier to a base name:

```
Northwind Coffee Roasters 001
Northwind Coffee Roasters 002
```

Avoid reusing the exact same business name across applications, even across different test runs.

**Signer name**

Use a varied signer name for every test application. A lightweight approach is to combine a fixed first name with a sequenced or randomized last name:

```
Morgan Reed-001
Morgan Reed-002
```

Or use a short random suffix:

```
Morgan Reed-8f3a
Morgan Reed-2c91
```

The signer name does not need to be a real person's name, but it should be unique per application to avoid duplicate-identity checks at the boarding layer.

#### Recommended Sandbox test path

A practical end-to-end Sandbox test usually looks like this:

1. Create an application with `submitImmediately: true`.
2. Wait for the signer email.
3. Open the hosted onboarding package and complete the signer flow.
4. After submit, watch for a second signature page for the Funds Transfer Agreement and complete that step as well.
5. Track the application using `GET /merchant-onboarding/applications/{applicationId}`.
6. If webhooks are enabled, confirm your endpoint receives lifecycle events.
7. After the signer submits, contact your Bead team with the merchant name so the Sandbox application can be manually advanced.
8. Continue checking status until the application is boarded and an `onboardedMerchantId` is returned.

#### Draft and attachment note

This page uses `submitImmediately: true` because it is the simplest workflow test.

If your test requires attachments, use the Draft flow instead:

```json
{
  "submitImmediately": false
}
```

Then upload attachments while the application is in Draft status and submit the Draft application for signature.

```http
POST /merchant-onboarding/applications/{applicationId}/submit
```

Use Application Attachments for the full Draft and attachment workflow.

#### Step 1: Create the application

You can test with either onboarding entry point.

Use the full application endpoint when you already have most merchant data and want to prefill the application as much as possible.

```http
POST /merchant-onboarding/applications
```

Use the short application endpoint when you want the hosted onboarding flow to collect most of the remaining information from the signer.

```http
POST /merchant-onboarding/applications-short
```

For a basic Sandbox workflow test, the short flow is usually the quickest path.

**Example short application request**

The example below uses a timestamped email, a sequenced business name, and a suffixed signer name — all following the uniqueness patterns described above.

> **This is an abbreviated workflow example, not a complete payload.** The `feeInformation` block below contains only three fees for illustration. A production application requires a full fee configuration aligned to the merchant's commercial agreement. Use [Sample Payload](/onboarding/sample-payload) for complete, copy-ready request bodies, and [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications) for fee structure guidance.

```json
{
  "merchantName": "Northwind Coffee Roasters 001",
  "partnerId": "{partnerId}",
  "partnerMid": "northwind-sandbox-001",
  "signerFirstName": "Morgan",
  "signerLastName": "Reed-001",
  "signerEmail": "qa+20260127143015@yourcompany.com",
  "isSettlementOnly": false,
  "isCommissionMerchant": false,
  "feeInformation": {
    "achSettlementFixedFee": {
      "sellRate": 0.79,
      "isBilledByPartner": false
    },
    "settlementReturnFixedFee": {
      "sellRate": 25,
      "isBilledByPartner": false
    },
    "monthlyMaintenanceFee": {
      "sellRate": 19.99,
      "isBilledByPartner": false
    }
  },
  "cryptoEnvironment": "sandbox",
  "submitImmediately": true
}
```

**Example response**

```json
{
  "applicationId": "app-123exampleid",
  "envelopeId": "env-1111-2222-3333-4444",
  "status": "sent"
}
```

After a successful create call, store these fields immediately:

* `applicationId`
* `envelopeId`
* `status`

You will use `applicationId` to check status, correlate webhooks, troubleshoot the application, and support the Sandbox manual advance step.

#### Step 2: Complete the signer flow

Once the application is created with `submitImmediately: true`, Bead sends the onboarding package to the signer using the signer details from your request.

For the short application flow, signer details are provided with:

* `signerFirstName`
* `signerLastName`
* `signerEmail`

For the full application flow, the application signer is identified in `merchantData.stakeholders`.

Use an email address you can access so you can complete the flow yourself during testing.

The signer should:

1. open the email invitation
2. review and complete the hosted onboarding package
3. sign all required documents and consents
4. click submit to finish the application flow

**Important Sandbox note**

After you click submit for the application, a second signature page for the Funds Transfer Agreement may appear.

Do not stop after the first submit action.

To complete the Sandbox workflow successfully, make sure the signer also completes the Funds Transfer Agreement signature step if it appears.

#### Step 3: Track status

Use the application status endpoint as your main source of truth for the current application state.

```http
GET /merchant-onboarding/applications/{applicationId}
```

Start with these response fields first:

* `id`
* `merchantName`
* `status`
* `onboardedMerchantId`

A common pattern is:

* use `status` to understand where the application is in the process
* use `merchantName` for support and reconciliation
* use `onboardedMerchantId` once the merchant is successfully boarded

For this Sandbox workflow, keep checking status as the signer completes the application and after the manual Sandbox advance step is performed.

#### Step 4: Test webhooks

Webhooks are optional for a basic test, but recommended for a complete integration test.

To test webhooks end to end:

1. Configure your onboarding webhook for the correct partner.
2. Use a simple HTTPS endpoint that logs requests and returns `2xx`.
3. Submit a test application with `submitImmediately: true`.
4. Capture the real webhook events generated by the application lifecycle.
5. Use `applicationId` from the webhook to fetch full application state when needed.

Treat onboarding webhooks as event notifications, not as the full application record.

Your webhook consumer should:

* use the webhook event `id` as the event-level idempotency key
* use `applicationId` as the application correlation key
* return `2xx` quickly
* call Get Status when full detail is needed

Depending on your flow, you may observe lifecycle moments such as signing complete, submitted, approved, declined, or needs more information.

#### Step 5: Complete the Sandbox-only manual advance step

In Sandbox, the application does not automatically move through the full internal workflow after signer submission.

After the signer completes the onboarding package and submits it, contact your Bead team and provide the merchant name used on the application.

Your Bead team can then coordinate the manual Sandbox advance needed to continue the end-to-end workflow.

This is a Sandbox testing requirement and should not be treated as the expected Production workflow.

#### Step 6: Confirm the boarded result

After the manual Sandbox advance is completed, continue checking the application status.

Your end goal is to confirm that:

* the application reaches its final boarded state
* `onboardedMerchantId` is returned
* your system can store and use that identifier for downstream workflows

At that point, you have validated the full application and onboarding workflow in Sandbox.

#### Expected results

A successful Sandbox workflow test should prove that:

* your system can create an onboarding application
* `submitImmediately: true` starts the signer flow without requiring a separate submit command
* the signer receives and completes the hosted onboarding package
* the Funds Transfer Agreement step is not missed
* your system can correlate `applicationId` across polling and webhook events
* your team understands the Sandbox-only manual advance step
* your integration can recognize when the merchant is boarded

#### Troubleshooting

**The signer did not receive the email**

Check that:

* the email address is valid and deliverable
* the email address is unique for this application
* the mailbox is one your team can access
* your organization does not block tagged or plus-addressed emails
* `submitImmediately` was set to `true`
* the application create request returned a successful response

**The application was rejected due to duplicate data**

The sponsor bank enforces uniqueness on several fields even in Sandbox. If your application is rejected and you are running repeated tests, check that:

* the signer email is unique for this application — see [Test data: email address requirements](/faqs-and-troubleshooting/environment-and-testing/test-data-uniqueness-requirements)
* the business name is distinct from previous test applications
* the signer name has not been reused across many applications

**The signer submitted the application, but the workflow is not complete**

Check that:

* the signer completed every page in the hosted flow
* the Funds Transfer Agreement signature page was completed if it appeared
* you are checking status using the correct `applicationId`
* the Sandbox manual advance step has been requested from your Bead team

**I received a webhook but need more detail**

Use `applicationId` from the webhook and call:

```http
GET /merchant-onboarding/applications/{applicationId}
```

**The application never reaches boarded in Sandbox**

This is the expected point to involve your Bead team.

Provide the merchant name used on the application so the Sandbox workflow can be manually advanced.

**I need to test attachments**

This page uses the immediate-submit path and does not require attachments.

To test attachments, create the application with:

```json
{
  "submitImmediately": false
}
```

Then follow the Application Attachments page before submitting the Draft application for signature.

#### Best practices

* Use the short application flow for fast Sandbox testing unless your production integration depends on full prefill.
* Set `submitImmediately` to `true` for the easiest end-to-end workflow test.
* Use `submitImmediately: false` only when you need to test Draft status and attachments.
* Save `applicationId` and `envelopeId` immediately after application creation.
* Use a real mailbox your team controls.
* Use a unique email address for every test application — generate it with a timestamp tag.
* Use a distinct registered business name for every test application — append a sequence number or short identifier.
* Use a varied signer name for every test application — append a sequence number or short random suffix.
* Use a unique merchant name or partner reference for every test.
* Test both polling and webhooks where possible.
* Make webhook processing idempotent.
* Treat the Sandbox manual advance step as a test-environment exception, not a production dependency.

#### Related pages

* [Submit Application](/onboarding/submit-application)
* [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications)
* [Application Attachments](/onboarding/application-attachments)
* [Get Status](/onboarding/get-status)
* [Webhooks for Application Events](/onboarding/webhooks-for-application-events)
* [Sample Payload](/onboarding/sample-payload)
* [Test data: email address requirements](/faqs-and-troubleshooting/environment-and-testing/test-data-uniqueness-requirements)


# Get Status

Use this endpoint to retrieve the current state of a merchant onboarding application.

This endpoint applies to both:

* Full applications created with `POST /merchant-onboarding/applications`
* Short applications created with `POST /merchant-onboarding/applications-short`

Use this endpoint when you need to:

* check the current onboarding status
* confirm whether the signer has viewed or completed the application
* determine whether the application is still under review or boarding
* retrieve the onboarded merchant identifier after approval
* support polling, troubleshooting, and reconciliation workflows

**Endpoint**

```http
GET /merchant-onboarding/applications/{applicationId}
```

**Path parameter**

| Parameter       | Description                                                                      |
| --------------- | -------------------------------------------------------------------------------- |
| `applicationId` | The onboarding application identifier returned when the application was created. |

**Authentication and headers**

Onboarding requests use API key authentication.

```http
X-Api-Key: {apiKey}
Accept: application/json
```

**Most important response fields**

Start with these fields first:

| Field                 | Description                                                                                                                |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `id`                  | The onboarding application identifier.                                                                                     |
| `merchantName`        | The merchant name associated with the application.                                                                         |
| `status`              | Overall onboarding status for the application.                                                                             |
| `onboardedMerchantId` | The merchant identifier created after successful onboarding. This is typically present once the merchant has been boarded. |

These fields usually tell you whether the application is still in progress, needs attention, or is ready for downstream merchant setup and usage.

**Example request**

```http
GET /merchant-onboarding/applications/{applicationId}
X-Api-Key: {apiKey}
Accept: application/json
```

**Example response**

```json
{
"id": "app-123exampleid",
"merchantName": "Northwind Coffee Roasters",
"partnerName": "Acme Payments",
"partnerId": "partner_123",
"status": "boarded",
"onboardedMerchantId": "merchant-555000111222"
}
```

**Understanding application status**

The `status` field represents the overall onboarding state of the application.

| Status             | Meaning                                                                                                                       | Typical next step                                                        |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `draft`            | The application was created but has not been sent to the signer yet. This is usually created with `submitImmediately: false`. | Upload attachments if needed, then submit the Draft application.         |
| `submitted`        | The application has been submitted into the onboarding workflow.                                                              | Continue monitoring status or webhooks.                                  |
| `changesRequested` | Additional information or correction is needed.                                                                               | Review the request, collect updated information, and resubmit as needed. |
| `resubmitted`      | A previously returned or changed application has been resubmitted.                                                            | Continue monitoring status or webhooks.                                  |
| `signing`          | The application has been sent to the signer and is awaiting completion.                                                       | Wait for the signer to view and complete the application.                |
| `viewed`           | The signer has opened the application but has not yet completed signing.                                                      | Wait for signer completion or follow up with the signer.                 |
| `signed`           | The signer has completed the signing flow.                                                                                    | Wait for review and downstream boarding steps.                           |
| `reviewing`        | The application is under review.                                                                                              | Monitor for approval, rejection, or request for changes.                 |
| `reviewed`         | Review has completed and the application is ready for the next onboarding step.                                               | Continue monitoring for boarding.                                        |
| `boarding`         | The application has moved into merchant and provider boarding.                                                                | Continue monitoring until boarded.                                       |
| `boarded`          | The merchant has completed onboarding.                                                                                        | Store `onboardedMerchantId` and use it for downstream workflows.         |
| `withdrawn`        | The application was withdrawn and is no longer moving forward.                                                                | Treat as a stopped application.                                          |
| `rejected`         | The application was rejected and will not board in its current form.                                                          | Review rejection details and determine next steps with your Bead team.   |

**Cancel eligibility is time-boxed to two of these statuses.** An application can only be cancelled (see [Cancel Application](/onboarding/cancel-application)) while it is in `viewed` or `signing` status. Once it moves to `submitted` or later, cancel is no longer available and calling that endpoint returns `409 Conflict`.

**Status flow examples**

**Immediate-submit flow**

When an application is created with `submitImmediately: true`, or when the field is omitted and the default immediate-submit behavior is used, the application is sent to the signer right away.

A typical flow may look like:

```
signing → viewed → signed → reviewing → reviewed → boarding → boarded
```

Use this path for the simplest onboarding workflow test.

**Draft and attachment flow**

When an application is created with `submitImmediately: false`, the application is created in Draft status.

A typical flow may look like:

```
draft → signing → viewed → signed → reviewing → reviewed → boarding → boarded
```

Use this path when attachments need to be added before the signer receives the application.

After attachments are uploaded, submit the Draft application for signature:

```http
POST /merchant-onboarding/applications/{applicationId}/submit
```

**How to use the response**

A common pattern is:

* use `status` to understand the overall application state
* use `onboardedMerchantId` once the merchant is successfully boarded
* use `merchantName` and `id` for display, support, and reconciliation workflows
* use webhooks for event-driven updates, then call this endpoint when you need the current full state

**When to poll**

Polling can be useful during testing, support workflows, and operational dashboards.

Recommended polling guidance:

* Poll during active testing when you are watching a single application move through the workflow.
* Use a reasonable interval, such as every 30 to 60 seconds, instead of tight loops.
* Stop polling once the application reaches a terminal or operationally complete state such as `boarded`, `withdrawn`, or `rejected`.
* Prefer webhooks for production event handling whenever possible.

**Webhooks and status checks**

Webhooks notify your system that something changed. This status endpoint gives you the current application state.

A recommended production pattern is:

1. Receive an onboarding webhook.
2. Use the webhook payload to identify the `applicationId`.
3. Call `GET /merchant-onboarding/applications/{applicationId}`.
4. Update your system using the current response from the status endpoint.

This keeps your system resilient if webhook events arrive out of order, are retried, or only include summary information.

**Provider and downstream boarding details**

The overall `status` field is the first field to check, but the response may also include provider or downstream application details.

Depending on the application and enabled services, the response may include information related to:

* agreement or signing status
* compliance or account services status
* banking application status
* crypto services application status
* digital wallet services application status
* onboarding exceptions

Use these details when you need to troubleshoot why an application is not yet boarded or when you need to understand which part of the onboarding process is still in progress.

**Onboarding exceptions**

If the response includes onboarding exceptions, review them before assuming the application is stalled.

An onboarding exception may indicate that a specific step requires attention, failed validation, or needs manual review.

Common operational handling:

* show a clear internal support state
* avoid asking the merchant to resubmit blindly
* coordinate with your Bead team if the exception is not self-serviceable
* continue checking status after the exception is resolved

**Application history**

For audit or troubleshooting workflows, use the application history endpoint.

```http
GET /merchant-onboarding/applications/{applicationId}/history
```

Use history when you need to understand how the application moved between states or when you need to support an operational investigation.

**Terminal states**

Treat these statuses as terminal or near-terminal for most integration workflows:

| Status      | How to handle                                                                              |
| ----------- | ------------------------------------------------------------------------------------------ |
| `boarded`   | Store `onboardedMerchantId` and proceed with downstream merchant setup or usage.           |
| `withdrawn` | Stop normal onboarding automation for this application.                                    |
| `rejected`  | Stop normal onboarding automation and review the reason or next steps with your Bead team. |

**Error responses**

| Code | Condition                                                                               |
| ---- | --------------------------------------------------------------------------------------- |
| 401  | Missing or invalid API key.                                                             |
| 403  | Authenticated but not permitted to view this application.                               |
| 404  | No application exists with the given `applicationId`, or it is not visible to your key. |

**Best practices**

* Store `applicationId` when the application is created.
* Use `applicationId` as the primary lookup key for status checks.
* Use lowercase status values exactly as returned by the API.
* Use `onboardedMerchantId` only after the merchant has successfully boarded.
* Use webhooks for production event handling and this endpoint for current state lookup.
* Avoid tight polling loops.
* Keep status handling flexible so your integration can support new statuses over time.
* Display clear internal support states for `changesRequested`, `withdrawn`, and `rejected`.
* Treat `viewed` as an informational checkpoint, not a terminal or blocking state — the signer is still expected to proceed to `signed`.
* For Sandbox testing, remember that some steps may require manual advancement by your Bead team.

**Related pages**

* [Submit Application](/onboarding/submit-application)
* [List Applications](/onboarding/list-applications)
* [Application Attachments](/onboarding/application-attachments)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)
* [Resend Application](/onboarding/resend-application)
* [Webhooks for Application Events](/onboarding/webhooks-for-application-events)
* [Sample Payload](/onboarding/sample-payload)
* [Reference Guide](/reference-guide)


# List Applications

Use this endpoint to retrieve a paginated list of merchant onboarding applications for a specific partner and its descendants.

Use this endpoint when you need to:

* find applications across a partner hierarchy without knowing individual `applicationId` values
* build an operational dashboard or queue of in-progress applications
* filter applications by status, date range, or free text search
* reconcile application activity for a partner and its downstream sub-partners

If you already have an `applicationId` and only need its current state, use [Get Status](/onboarding/get-status) instead.

#### Endpoint

```http
GET /Partners/{id}/applications
```

#### Path parameter

| Parameter | Description                                                                                                              |
| --------- | ------------------------------------------------------------------------------------------------------------------------ |
| `id`      | The `partnerId` to scope results to. Returns applications for this partner and its descendants in the partner hierarchy. |

#### Query parameters

| Parameter       | Type      | Required | Description                                                                                                                                                                                                  |
| --------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `From`          | date-time | No       | Filter by `updatedAt` from this timestamp (ISO 8601 UTC). Defaults to 30 days before `To`.                                                                                                                   |
| `To`            | date-time | No       | Filter by `updatedAt` up to this timestamp (ISO 8601 UTC). Defaults to the current UTC time.                                                                                                                 |
| `Statuses`      | array     | No       | Filter by one or more application statuses: `draft`, `submitted`, `changesRequested`, `resubmitted`, `signing`, `signed`, `viewed`, `reviewing`, `reviewed`, `boarding`, `boarded`, `withdrawn`, `rejected`. |
| `Search`        | string    | No       | Free text search across fields such as merchant name, signer email, or partner external ID.                                                                                                                  |
| `Page`          | integer   | No       | Zero-based page number. Defaults to `0`.                                                                                                                                                                     |
| `PageSize`      | integer   | No       | Number of results per page. Defaults to `50`, max `100`.                                                                                                                                                     |
| `SortBy`        | string    | No       | Field to sort by. Supported values: `merchantName`, `status`, `updated`.                                                                                                                                     |
| `SortDirection` | string    | No       | `asc` or `desc`.                                                                                                                                                                                             |

#### Authentication and headers

```http
X-Api-Key: {apiKey}
Accept: application/json
```

#### Example request

```bash
curl -X GET "https://api.test.devs.beadpay.io/Partners/{id}/applications?Statuses=boarding&Page=0&PageSize=50" \
     -H "X-Api-Key: {apiKey}" \
     -H "Accept: application/json"
```

#### Successful response — 200

The response is a paginated wrapper containing an array of application objects.

```json
{
  "data": [
    {
      "id": "app-123exampleid",
      "merchantName": "Northwind Coffee Roasters",
      "partnerName": "Acme Payments",
      "partnerId": "partner_123",
      "isSettlementOnly": false,
      "isCommissionMerchant": false,
      "commissionMerchantId": null,
      "representsPartnerId": null,
      "status": "boarding",
      "updated": "2026-07-28T14:32:00.000Z",
      "signer": {
        "email": "jane.doe@example.com",
        "name": "Jane Doe",
        "firstName": "Jane",
        "lastName": "Doe",
        "position": "Owner",
        "declinedReason": null,
        "ipAddress": "203.0.113.10",
        "language": "en",
        "userAgent": "Mozilla/5.0"
      },
      "onboardedMerchantId": null,
      "agreementApplication": null,
      "complianceApplication": null,
      "accountServicesApplication": null,
      "bankingApplication": null,
      "cryptoServicesApplications": null,
      "digitalWalletServicesApplications": null,
      "cryptoServicesApplication": null,
      "digitalWalletServicesApplication": null,
      "onboardingExceptions": null
    }
  ],
  "total": 1,
  "page": 0
}
```

#### Response fields

| Field   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data`  | Array of application objects. Each object uses the same shape returned by [Get Status](/onboarding/get-status): `id`, `merchantName`, `partnerName`, `partnerId`, `status`, `updated`, `signer`, `onboardedMerchantId`, plus provider and downstream boarding detail fields (`agreementApplication`, `complianceApplication`, `accountServicesApplication`, `bankingApplication`, `cryptoServicesApplications`, `digitalWalletServicesApplications`, `onboardingExceptions`) that populate as the application progresses. See Get Status for how to interpret these details. |
| `total` | Total number of applications matching the query.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `page`  | Current page number, zero-indexed.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |

#### Error responses

| Code | Condition                                                              |
| ---- | ---------------------------------------------------------------------- |
| 401  | Missing or invalid API key.                                            |
| 403  | Authenticated but not permitted to list applications for this partner. |

#### Best practices

* Scope by the highest-level partner ID that covers all the applications you need — results include descendants automatically, so you don't need to call this once per sub-partner.
* Use `Statuses` to filter to the applications you care about (for example, `boarding` or `changesRequested`) rather than paging through the full result set.
* Use `Search` to locate a specific application by merchant name, signer email, or partner external ID instead of scanning pages manually.
* Store `total` to drive pagination in dashboards or queues.
* Use this endpoint for discovery and operational views; once you have an `applicationId`, use [Get Status](/onboarding/get-status) for the authoritative current state of that single application.

#### A note on the deprecated List Applications endpoint

`GET /merchant-onboarding/applications` is deprecated. Use this endpoint, `GET /Partners/{id}/applications`, instead — it returns the same application data, scoped to a partner and its descendants.

**Related pages**

* [Get Status](/onboarding/get-status)
* [Submit Application](/onboarding/submit-application)
* [Resend Application](/onboarding/resend-application)
* [Partner Management](/entity-management/partner-management)
* [Reference Guide](/reference-guide)


# Resend Application

Use this endpoint to send an existing onboarding application back to the same merchant signer so they can complete, correct, or re-submit the package.

This endpoint applies to both:

* Full applications created with `POST /merchant-onboarding/applications`
* Short applications created with `POST /merchant-onboarding/applications-short`

Resend keeps the same onboarding application record and sends the package back to the signer associated with that application.

### Resend vs. submit Draft application

Do not use Resend Application to submit a Draft application for the first time.

| Action                         | Endpoint                                                        | Use when                                                                                                                                                     |
| ------------------------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Submit a Draft application     | `POST /merchant-onboarding/applications/{applicationId}/submit` | The application was created with `submitImmediately: false`, attachments may have been added, and you are ready to send it to the signer for the first time. |
| Resend an existing application | `PUT /merchant-onboarding/applications/{applicationId}`         | The application has already entered the signer-facing flow and needs to be sent back to the same signer.                                                     |

If you created the application with `submitImmediately: false`, follow the Application Attachments workflow first, then call the Draft submit endpoint.

```http
POST /merchant-onboarding/applications/{applicationId}/submit
```

Use this Resend Application page only when you need to resend or reopen an existing application package for the same signer.

### When to use Resend Application

Use Resend Application when you want to:

* re-send an existing onboarding package to the same merchant signer
* allow the merchant signer to correct missing or invalid information
* let the signer complete steps they did not finish
* re-open the hosted onboarding flow for a previously sent application
* keep the same application record instead of creating a new one
* preserve application history while restarting the signer-facing flow

Typical examples include:

* the signer did not complete the package
* the signer needs to correct information
* the application moved to `changesRequested`
* the same signer needs another opportunity to submit the package
* your support or operations team wants to continue the original application instead of starting over

### When not to use Resend Application

Do not use this endpoint when:

* the application is still in Draft status and has not been sent to the signer yet
* you need to upload attachments before the signer receives the application
* you need to change the signer
* you want to create a new application record
* the existing application was withdrawn, rejected, or should no longer continue

For Draft applications, use:

```http
POST /merchant-onboarding/applications/{applicationId}/submit
```

For attachments, use the Application Attachments page before submitting the Draft application.

### Endpoint

```http
PUT /merchant-onboarding/applications/{applicationId}
```

### Path parameter

| Parameter       | Description                                                                      |
| --------------- | -------------------------------------------------------------------------------- |
| `applicationId` | The onboarding application identifier returned when the application was created. |

### Authentication and headers

Onboarding requests use API key authentication.

```http
X-Api-Key: {apiKey}
Content-Type: application/json
Accept: application/json
```

### What this endpoint does

Resend Application sends the existing onboarding package back to the signer associated with the application.

Use this endpoint when the signer needs to:

* re-open the hosted onboarding flow
* correct information
* complete missing steps
* re-submit the application after prior review or follow-up

This flow is intended to keep the original application record in place rather than starting over with a new application.

### Typical request pattern

A common resend pattern is:

1. Retrieve the current application status.
2. Confirm that the existing application should continue.
3. Confirm that the package should go back to the same signer.
4. Call `PUT /merchant-onboarding/applications/{applicationId}`.
5. Allow the merchant signer to re-open the hosted flow and submit corrections.
6. Track the updated application using `GET /merchant-onboarding/applications/{applicationId}`.

### Example request

```http
PUT /merchant-onboarding/applications/{applicationId}
X-Api-Key: {apiKey}
Content-Type: application/json
Accept: application/json
```

The current endpoint does not require a request body.

### Example response

```json
{
  "applicationId": "app-123exampleid",
  "envelopeId": "env-1111-2222-3333-4444",
  "status": "resubmitted"
}
```

Store the returned values and continue tracking the application by `applicationId`.

### Example flow in a portal

A typical portal-driven resend flow looks like this:

1. A user sees that an onboarding application still needs action.
2. Your system calls `GET /merchant-onboarding/applications/{applicationId}`.
3. Your portal shows the current status and indicates that action is required.
4. A user selects a resend action.
5. Your system calls `PUT /merchant-onboarding/applications/{applicationId}`.
6. The merchant signer receives the updated onboarding invitation and completes the hosted flow.
7. Your system continues tracking progress with `GET /merchant-onboarding/applications/{applicationId}`.

### Status handling

Before resending, check the current application status.

```http
GET /merchant-onboarding/applications/{applicationId}
```

Resend is most useful when the application is still intended to continue and the same signer should take action.

Common statuses that may lead to a resend include:

| Status             | Resend guidance                                                                                                |
| ------------------ | -------------------------------------------------------------------------------------------------------------- |
| `signing`          | Resend may be useful if the signer did not complete the package or needs a new invitation.                     |
| `changesRequested` | Resend may be useful after the signer needs to correct or complete information.                                |
| `resubmitted`      | Continue tracking after a resend or correction flow.                                                           |
| `signed`           | Usually do not resend unless your Bead team or operations process indicates the signer must take action again. |
| `reviewing`        | Usually wait for review to complete unless directed otherwise.                                                 |
| `boarding`         | Usually do not resend; the application has moved into downstream boarding.                                     |
| `boarded`          | Do not resend; onboarding is complete.                                                                         |
| `withdrawn`        | Do not resend unless your Bead team confirms the application should be reopened.                               |
| `rejected`         | Do not resend unless your Bead team confirms the application should be reopened or corrected.                  |

If you are not sure whether an application should be resent, retrieve the latest status first and coordinate with your Bead team if the status is terminal or unclear.

### Resend and attachments

Attachments can only be added while an application is in Draft status.

If the application has already left Draft status, do not use Resend Application to add or replace attachments. Resend sends the existing package back to the signer; it does not reopen the Draft attachment window.

To include attachments before the signer receives the application:

1. Create the application with `submitImmediately: false`.
2. Upload attachments while the application is in Draft status.
3. Submit the Draft application for signature.

```http
POST /merchant-onboarding/applications/{applicationId}/submit
```

### Resend and signer identity

Resend is intended for the same signer associated with the existing application.

Do not use Resend Application when you need to change the signer. If the signer needs to change, coordinate with your Bead team or create a new application if that is the correct operational path.

### Error handling

| Scenario                                               | Expected result                                                              |
| ------------------------------------------------------ | ---------------------------------------------------------------------------- |
| Unknown `applicationId`                                | The request returns not found.                                               |
| Application is not eligible for resend                 | The request may fail with a validation or conflict response.                 |
| API key is missing or invalid                          | The request returns unauthorized or forbidden.                               |
| Application is already boarded                         | Do not resend; use the boarded merchant record for downstream workflows.     |
| Application was created as Draft and not submitted yet | Use `POST /merchant-onboarding/applications/{applicationId}/submit` instead. |

### Best practices

* Retrieve the current application status before resending.
* Use resend only when the same application should continue.
* Use resend only when the same signer should receive the package again.
* Do not use resend for Draft submission.
* Do not use resend to add attachments after the application leaves Draft status.
* Reuse the existing `applicationId` when you want to preserve application history and status tracking.
* Prefer resend over creating a new application when the original application is still the correct record to continue.
* Keep your portal messaging clear so users know whether they are resending an existing application, submitting a Draft application, or creating a new one.
* Continue tracking the application with Get Status after the resend.

### Related pages

* [Submit Application](/onboarding/submit-application)
* [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications)
* [Application Attachments](/onboarding/application-attachments)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)
* [Get Status](/onboarding/get-status)
* [Webhooks for Application Events](/onboarding/webhooks-for-application-events)
* [Sample Payload](/onboarding/sample-payload)
* [Reference Guide](/reference-guide)


# Cancel Application

Use this endpoint to cancel the signing process for an existing onboarding application by revoking the signer's active token.

This endpoint applies to both:

* Full applications created with `POST /merchant-onboarding/applications`
* Short applications created with `POST /merchant-onboarding/applications-short`

Cancel revokes the signing token so the signer can no longer complete the current session. It does not delete the application record.

{% hint style="warning" %} **Cancel is only available while an application is in `viewed` or `signing` status.** Once an application has been submitted — including `submitted`, `changesRequested`, `resubmitted`, and every status beyond — the cancel window is closed. Calling this endpoint on an application outside `viewed`/`signing` returns an error; it is not a judgment call to make case by case. {% endhint %}

**Cancel vs. Resend**

These two commands are related but serve different purposes.

| Action                     | Endpoint                                                | Use when                                                                                                |
| -------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Cancel the signing process | `POST /merchant-onboarding/{applicationId}/cancel`      | You need to stop the active signing session and prevent the current signer from completing the package. |
| Resend to the same signer  | `PUT /merchant-onboarding/applications/{applicationId}` | The application should continue and the same signer needs another opportunity to complete the package.  |

If you want to send the application back to the same signer without stopping the process, use [Resend Application](/onboarding/resend-application) instead.

**When to use Cancel Application**

Use Cancel Application when you need to:

* stop the current signer from completing the package
* prevent a signing session that was sent to the wrong recipient from being completed
* halt onboarding for a merchant that has withdrawn from the process
* stop the signing flow before creating a corrected application
* revoke access for a signer whose details were entered incorrectly

Typical examples include:

* the application was sent to the wrong email address
* the signer is no longer the correct point of contact
* the merchant has asked to stop onboarding
* your operations team needs to halt the process before it progresses further

All of the above only apply **before submission** — while the application is still in `viewed` or `signing` status. If the application has already been submitted, Cancel Application is no longer the right tool; see [When not to use Cancel Application](#when-not-to-use-cancel-application) below.

**When not to use Cancel Application**

Do not use this endpoint once an application has been submitted. This includes:

* the application is in `draft` status and has not yet been sent to a signer — there is no signing token to revoke
* the application has been submitted and is in `submitted`, `changesRequested`, or `resubmitted` status — the submission window has closed
* the application has already been signed and is in review or boarding (`signed`, `reviewing`, `reviewed`, `boarding`)
* the application is already in a terminal state (`boarded`, `withdrawn`, `rejected`)
* you want to send the package back to the same signer — use Resend Application instead

For Draft applications that need to be discarded, coordinate with your Bead team.

**Endpoint**

```http
POST /merchant-onboarding/{applicationId}/cancel
```

**Path parameter**

<table><thead><tr><th width="174">Parameter</th><th>Description</th></tr></thead><tbody><tr><td><code>applicationId</code></td><td>The onboarding application identifier returned when the application was created.</td></tr></tbody></table>

**Authentication and headers**

Onboarding requests use API key authentication.

```http
X-Api-Key: {apiKey}
Accept: application/json
```

No request body is required.

**What this endpoint does**

Cancel Application revokes the active signing token for the specified application. After the token is revoked, the current signer can no longer access or complete the hosted onboarding and signing experience.

The application record is preserved. Retrieve the application status after cancelling to confirm the resulting state before deciding on next steps.

**Typical request pattern**

A common cancel pattern is:

1. Retrieve the current application status with `GET /merchant-onboarding/applications/{applicationId}`.
2. Confirm the application is in `viewed` or `signing` status.
3. Call `POST /merchant-onboarding/{applicationId}/cancel`.
4. Retrieve the updated status to confirm the application state.
5. Decide on next steps — whether to create a new application, coordinate with your Bead team, or take no further action.

**Example request**

```http
POST /merchant-onboarding/{applicationId}/cancel
X-Api-Key: {apiKey}
Accept: application/json
```

No request body is required.

**Example response**

A successful cancel returns HTTP `200` with no response body.

**Status handling**

**Cancel is only valid while the application is in `viewed` or `signing` status.** Retrieve the current application status before cancelling — do not attempt this call against any other status.

```http
GET /merchant-onboarding/applications/{applicationId}
```

<table><thead><tr><th width="188">Status</th><th width="152" align="center">Cancellable?</th><th>Cancel guidance</th></tr></thead><tbody><tr><td><code>draft</code></td><td align="center">No</td><td>The application has not been sent to a signer yet. No signing token exists to revoke.</td></tr><tr><td><code>submitted</code></td><td align="center">No</td><td>The application has been submitted. The cancel window has closed — this is a submission-stage status, not a pre-submission one.</td></tr><tr><td><code>signing</code></td><td align="center"><strong>Yes</strong></td><td>The signer has not yet completed the package. Cancel is appropriate to stop the signing session.</td></tr><tr><td><code>viewed</code></td><td align="center"><strong>Yes</strong></td><td>The signer has opened but not completed the package. Cancel is appropriate to stop the signer from proceeding.</td></tr><tr><td><code>changesRequested</code></td><td align="center">No</td><td>The application has been submitted and returned for changes. This is a post-submission status — cancel is not applicable.</td></tr><tr><td><code>resubmitted</code></td><td align="center">No</td><td>The application has been resubmitted after changes. This is a post-submission status — cancel is not applicable.</td></tr><tr><td><code>signed</code></td><td align="center">No</td><td>The application has already been signed. Cancelling is not applicable.</td></tr><tr><td><code>reviewing</code></td><td align="center">No</td><td>The application is under review. Coordinate with your Bead team if the process needs to stop.</td></tr><tr><td><code>boarding</code></td><td align="center">No</td><td>The application is in downstream boarding. Coordinate with your Bead team if the process needs to stop.</td></tr><tr><td><code>boarded</code></td><td align="center">No</td><td>Onboarding is complete — a terminal state.</td></tr><tr><td><code>withdrawn</code></td><td align="center">No</td><td>Already in a terminal state.</td></tr><tr><td><code>rejected</code></td><td align="center">No</td><td>Already in a terminal state.</td></tr></tbody></table>

After calling cancel, retrieve the application again to confirm the status before taking further action.

**Cancel and creating a new application**

Cancelling an application stops the current signing process but does not automatically create a replacement application.

If you need to restart onboarding with a different signer or corrected merchant data, create a new application after confirming the cancel succeeded.

Reuse the same `partnerId` and merchant reference when the underlying merchant relationship continues.

**Error handling**

| Scenario                                                                                                 | Expected result                                                            |
| -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| Unknown `applicationId`                                                                                  | `404 Not Found`.                                                           |
| Application is not in `viewed` or `signing` status (already submitted, already terminal, or still Draft) | `409 Conflict` — cancel is not valid for the application's current status. |
| Malformed request                                                                                        | `400 Bad Request`.                                                         |
| API key is missing or invalid                                                                            | `401 Unauthorized`.                                                        |
| API key does not have access to the application                                                          | `403 Forbidden`.                                                           |

If you need to stop an application that has already returned `409 Conflict` on cancel, do not retry the call — the status has moved past the point where cancel applies. Coordinate with your Bead team on next steps instead.

**Best practices**

* Retrieve the current application status before cancelling — cancel is only valid for `viewed` or `signing` status.
* Do not call cancel on Draft applications — there is no signing token to revoke.
* Do not call cancel on an application that has already been submitted (`submitted`, `changesRequested`, `resubmitted`, or beyond) — it will return `409 Conflict`.
* If you want an application to remain cancellable, request cancellation before the signer submits it. Once submitted, that window is closed and cannot be reopened from the API.
* After cancelling, retrieve the application status to confirm the resulting state before deciding on next steps.
* If the intent is to restart onboarding with the same signer, use Resend Application instead of cancel.
* If the intent is to restart onboarding with a different signer or corrected data, create a new application after confirming the cancel succeeded.
* Coordinate with your Bead team if you are unsure whether a cancellation is appropriate for a given status.

**Related pages**

* [Submit Application](/onboarding/submit-application)
* [Get Status](/onboarding/get-status)
* [Resend Application](/onboarding/resend-application)
* [Webhooks for Application Events](/onboarding/webhooks-for-application-events)
* [Sample Payload](/onboarding/sample-payload)
* [Reference Guide](/reference-guide)


# Webhooks for Application Events

Webhooks let Bead notify your system when important events occur on a merchant onboarding application.

Instead of relying only on repeated polling, your server receives an HTTP `POST` when an application reaches a meaningful lifecycle moment.

Use onboarding webhooks when you want to:

* keep your own merchant records in sync with application progress
* trigger internal workflows when an application is signed, reviewed, or boarded
* notify internal teams when onboarding activity requires follow-up
* reduce repeated polling against the Get Status endpoint
* support operational dashboards and reconciliation workflows

Onboarding webhooks are configured at the partner level. Once configured, Bead sends onboarding-related events for applications associated with that partner.

### How onboarding webhooks work

A typical webhook workflow is:

1. You create or manage onboarding applications under a partner.
2. You configure a partner-level onboarding webhook endpoint.
3. Bead sends an event to your endpoint when an application lifecycle event occurs.
4. Your system validates the request, records the event, and updates internal state.
5. If you need the full current application state, call `GET /merchant-onboarding/applications/{applicationId}` using the `applicationId` from the webhook.

Webhook payloads are event notifications. They are not intended to replace the Get Status endpoint as the full source of application truth.

### Supported onboarding events

Onboarding webhook configuration supports these event types:

| Event type            | Meaning                                                                      | Recommended handling                                                                                        |
| --------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `applicationSigned`   | The signer completed the signing step represented by the onboarding package. | Record the event and call Get Status to confirm the current application state.                              |
| `applicationReviewed` | The application review step produced a review event.                         | Call Get Status to determine whether the application is reviewed, still needs action, or has moved forward. |
| `merchantBoarded`     | The merchant has completed boarding.                                         | Call Get Status and store `onboardedMerchantId` for downstream workflows.                                   |

Design your handler to tolerate additional event types in the future. New event types should not break your webhook consumer.

### Configure the onboarding webhook

To receive onboarding events, configure a webhook URL for your partner.

You can register, update, or remove a partner-level webhook configuration.

| Action                                   | Endpoint                                          |
| ---------------------------------------- | ------------------------------------------------- |
| Register webhook configuration           | `POST /merchant-onboarding/{partnerId}/webhook`   |
| Register or update webhook configuration | `PUT /merchant-onboarding/{partnerId}/webhook`    |
| Remove webhook configuration             | `DELETE /merchant-onboarding/{partnerId}/webhook` |

For most integrations, use `PUT` so the same command can create or update the configuration.

### Endpoint

```http
PUT /merchant-onboarding/{partnerId}/webhook
```

### Authentication and headers

Onboarding requests use API key authentication.

```http
X-Api-Key: {apiKey}
Content-Type: application/json
Accept: application/json
```

### Path parameter

| Parameter   | Description                                                                                                                                   |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `partnerId` | Your partner identifier. The webhook configuration is stored per partner and applies to onboarding applications submitted under that partner. |

### Request body

Use a request body with your webhook endpoint URL and the onboarding event types you want to receive.

```json
{
  "endpointUrl": "https://partner.example.com/webhooks/onboarding",
  "eventTypes": [
    "applicationSigned",
    "applicationReviewed",
    "merchantBoarded"
  ]
}
```

### Field details

| Field         | Required | Description                                                              |
| ------------- | -------- | ------------------------------------------------------------------------ |
| `endpointUrl` | Yes      | HTTPS URL of your server that will receive onboarding webhook callbacks. |
| `eventTypes`  | Yes      | List of onboarding event types to deliver to this endpoint.              |

Your `endpointUrl` should be stable, publicly reachable, HTTPS-only, and controlled by your organization.

You can call the `PUT` endpoint more than once to update the same partner’s webhook configuration.

### Response

The response confirms the current webhook configuration.

Typical fields include:

* `partnerId`
* `endpointUrl`
* `isEnabled`
* `webhookSecret`
* `eventTypes`

Example response:

```json
{
  "partnerId": "partner_123",
  "endpointUrl": "https://partner.example.com/webhooks/onboarding",
  "isEnabled": true,
  "webhookSecret": "whsec_example_secret",
  "eventTypes": [
    "applicationSigned",
    "applicationReviewed",
    "merchantBoarded"
  ]
}
```

Store the `webhookSecret` securely. Treat it as a secret and do not expose it in client-side code.

### Receiving webhook events

After you configure an onboarding webhook, Bead sends HTTP `POST` requests to your `endpointUrl` when selected onboarding events occur.

High-level behavior:

* method is `POST`
* content type is JSON
* body contains event metadata plus a small application summary
* your endpoint should return a `2xx` response when processing succeeds

Onboarding webhook payloads are intentionally lean. They are not full application records.

Use the webhook payload to:

* identify which application changed
* identify what event occurred
* correlate the event to your own merchant or partner records
* trigger follow-up actions
* decide whether to fetch the full application status

Use `GET /merchant-onboarding/applications/{applicationId}` when you need the full current state of the application.

### Security and verification

Treat onboarding webhooks as a privileged integration path.

Recommended practices:

* use HTTPS for your webhook endpoint
* accept only `POST` on your webhook route
* preserve the raw request body and request headers
* validate the request body against expected fields
* use the `webhookSecret` from the webhook configuration for verification where your program enables signing or shared-secret verification
* optionally restrict source IPs or networks where appropriate for your environment

The exact signing or authentication mechanism may vary by environment or program. If your environment provides a signature header or specific signing format, validate that before processing the event.

### Payload format

Onboarding application events use a small event envelope with top-level metadata and a nested `data` object.

#### Top-level fields

| Field           | Type   | Description                                          |
| --------------- | ------ | ---------------------------------------------------- |
| `id`            | string | Unique webhook event identifier.                     |
| `type`          | string | Event type that describes what occurred.             |
| `applicationId` | string | Primary onboarding application identifier.           |
| `createdAt`     | string | Timestamp when the event was created.                |
| `data`          | object | Small application summary associated with the event. |

#### `data` object fields

| Field                    | Type           | Description                                                                          |
| ------------------------ | -------------- | ------------------------------------------------------------------------------------ |
| `data.id`                | string         | Application identifier within the nested payload. This should match `applicationId`. |
| `data.merchantName`      | string \| null | Merchant name associated with the application.                                       |
| `data.partnerName`       | string \| null | Partner name associated with the application.                                        |
| `data.partnerId`         | string         | Partner identifier associated with the application.                                  |
| `data.partnerExternalId` | string \| null | Your external partner reference, when available.                                     |

### Example payload

Below is an example `applicationSigned` onboarding webhook event.

```json
{
  "id": "evt_example_application_signed_001",
  "type": "applicationSigned",
  "applicationId": "app_example_001",
  "createdAt": "2026-03-30T20:46:15.111218Z",
  "data": {
    "id": "app_example_001",
    "merchantName": "Example Merchant",
    "partnerName": "Example Partner",
    "partnerId": "partner_example_001",
    "partnerExternalId": "example-partner-001"
  }
}
```

### Field guidance

#### `id`

This is the webhook event identifier. Use it as the primary event-level idempotency key whenever possible.

#### `type`

This tells you what happened.

Supported onboarding event types include:

* `applicationSigned`
* `applicationReviewed`
* `merchantBoarded`

Use `type` as the primary event classifier, but do not treat it as the complete application state. Call Get Status when your system needs to know the current application status.

#### `applicationId`

This is the primary application correlation key.

Use it to:

* link the webhook to your internal onboarding record
* call Get Status for full application detail
* reconcile lifecycle changes over time
* support internal dashboards and support workflows

#### `createdAt`

This is the event timestamp. It is useful for event ordering, auditing, and deduplication.

Do not rely on events arriving in strict chronological order. Use `createdAt` for audit context and call Get Status to confirm the current application state.

#### `data`

This object contains a lightweight application summary.

It is useful for:

* displaying a readable merchant or partner name in logs and dashboards
* routing to the correct partner context
* correlating internal references without requiring an immediate follow-up API call

### Event handling expectations

At minimum, your webhook handler should store:

* `id`
* `type`
* `applicationId`
* `createdAt`

In most integrations, you should also store:

* `data.partnerId`
* `data.partnerExternalId`
* `data.merchantName`
* the raw request body
* the request headers
* your processing result

Treat the payload as an event trigger, not a source of full application truth.

### Recommended processing model

A recommended webhook processing model is:

1. Receive the request.
2. Preserve the raw request body and headers.
3. Verify authenticity using your configured webhook security model.
4. Parse the JSON payload.
5. Use `id` as the event identifier and `applicationId` as the application correlation key.
6. Apply idempotent processing so duplicate deliveries do not create duplicate actions.
7. Call Get Status when you need the full current application state.
8. Update your internal systems.
9. Return a `2xx` response quickly.

### Idempotency and duplicate handling

Treat webhook delivery as at least once.

A good idempotent processing strategy is:

* primary event key: `id`
* fallback key: `applicationId` + `type` + `createdAt`

Your system should be able to safely ignore or reprocess duplicate webhook deliveries without creating duplicate tasks, records, or notifications.

### Using webhooks with Get Status

Webhooks and Get Status work best together.

Use the webhook to:

* detect that something changed
* know which application changed
* route the event internally
* trigger follow-up workflows

Use Get Status to:

* retrieve the current full application state
* confirm the latest status before irreversible actions
* check for `onboardedMerchantId` after `merchantBoarded`
* surface complete details in your portal or internal tools

This is especially important because onboarding webhook events are intentionally smaller than the full application record.

### Mapping events to application status

Webhook event types and application statuses are related, but they are not the same thing.

| Webhook event         | Related status context                                                                                           | Recommended action                                                                            |
| --------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `applicationSigned`   | Often associated with signer completion and statuses such as `signed`, `reviewing`, or later states.             | Call Get Status and update your internal record using the returned status.                    |
| `applicationReviewed` | Often associated with review completion and statuses such as `reviewed`, `boarding`, `withdrawn`, or `rejected`. | Call Get Status and determine the next operational step from the current status.              |
| `merchantBoarded`     | Usually associated with the `boarded` status and presence of `onboardedMerchantId`.                              | Call Get Status, store `onboardedMerchantId`, and proceed with downstream merchant workflows. |

Do not assume the webhook event alone contains the final current status. Always call Get Status when the next action depends on the exact current application state.

### Using webhooks with manual risk and review workflows

Webhooks also complement manual risk, review, and operations workflows.

A common pattern is:

1. receive an onboarding webhook event
2. call Get Status for the latest state
3. surface the current state to your operations team or merchant-facing portal
4. take the next internal action based on the returned status and any onboarding exceptions

This is especially useful when an application needs review, follow-up, or operational investigation.

### Testing your onboarding webhook

Before going live:

1. Implement a simple listener endpoint that logs incoming requests and returns `200 OK`.
2. Configure your Sandbox partner to use that endpoint.
3. Include the event types you want to test.
4. Submit test onboarding applications.
5. Capture real webhook events.
6. Confirm your processing is idempotent.
7. Confirm your system can use `applicationId` to fetch full application state.

Recommended events to test over time:

* `applicationSigned`
* `applicationReviewed`
* `merchantBoarded`

For a complete Sandbox workflow test, use Test the Full Onboarding Workflow in Sandbox and confirm your webhook listener receives the expected lifecycle events as the application moves through signing, review, and boarding.

### Troubleshooting

#### I am not receiving onboarding webhook events

Check:

* the webhook is configured on the expected `partnerId`
* the applications were created under that partner
* the configured `eventTypes` include the event you expect to receive
* your endpoint is publicly reachable
* your endpoint accepts `POST`
* your endpoint returns a `2xx` response
* your endpoint does not time out

#### I received a webhook but need more application detail

Use `applicationId` and call:

```http
GET /merchant-onboarding/applications/{applicationId}
```

#### I am unsure how to verify authenticity

Use the configured `webhookSecret` and your program’s signing or shared-secret verification method.

If your environment uses a specific header or signature format, validate that before processing.

#### The webhook payload does not include everything I need

This is expected. The onboarding webhook payload is designed as a lightweight event notification.

Use it to trigger follow-up logic, then call Get Status when you need the full application record.

#### I received the same webhook more than once

This can happen with at-least-once webhook delivery.

Use `id` as your event-level idempotency key. Your system should safely ignore duplicates or reprocess them without creating duplicate tasks, records, emails, or status changes.

#### I received an event type I do not recognize

Store the event, avoid failing your handler, and return `2xx` if the request is otherwise valid.

Then call Get Status using `applicationId` to determine the current application state.

### Best practices

* Treat onboarding webhook events as event notifications, not full application records.
* Configure only the event types your system is prepared to process.
* Use `id` as the primary event identifier.
* Use `applicationId` as the primary application correlation key.
* Return a `2xx` response quickly.
* Make your consumer idempotent.
* Keep your webhook endpoint HTTPS-only.
* Store `webhookSecret` securely.
* Capture and test real Sandbox events before Production.
* Call Get Status before taking irreversible or operationally sensitive actions.
* Design your handler to tolerate new event types or additional fields in the future.

### Related pages

* [Submit Application](/onboarding/submit-application)
* [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications)
* [Application Attachments](/onboarding/application-attachments)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)
* [Get Status](/onboarding/get-status)
* [Resend Application](/onboarding/resend-application)
* [Sample Payload](/onboarding/sample-payload)


# Manual Risk Feedback

{% hint style="danger" %}
Castro note: Hidden because I think this is an internal use only method. But keeping as a placeholder until confirmed.
{% endhint %}

Manual Risk Feedback lets your risk and compliance teams send a final decision or additional classification for an onboarding application into the system. This is used when a human reviewer has evaluated the merchant and wants to mark the application as approved, declined, or fraud based on manual review.

Use this endpoint when you want to:

* Record a manual decision from risk or compliance for a specific application
* Mark an application as fraud after investigation
* Confirm an approval decision that is based on human review rather than only automated checks
* Decline an application with a clear reason and reviewer record

The operation is designed to carry a small, structured payload with the decision, the reviewer, and the reason.

### Endpoint

```http
POST /merchant-onboarding/{applicationId}/feedback
```

Path parameter

* `applicationId`\
  The onboarding application identifier returned when you created the application with the submit endpoints.

Authentication

* Requires a valid OAuth access token for the bead-integrator client.
* Use the standard Authorization header.

Headers

```http
Authorization: Bearer {access_token}
Content-Type: application/json
Accept: application/json
```

### Request body

The request body uses the `SubmitFeedbackRequest` model.

Fields

* `stakeholderEmail`
  * Required
  * The email address of the merchant stakeholder the feedback applies to.
  * Typically this is the primary signer or main point of contact associated with the application.
* `status`
  * Required
  * The feedback status you are applying to the application.
  * Allowed values:
    * `approved`
    * `declined`
    * `fraud`
* `reason`
  * Required
  * A short, human-readable reason for this decision.
  * Example: `"Insufficient documentation for ownership structure"` or `"Confirmed synthetic identity fraud"`.
* `description`
  * Optional
  * Longer free-text description providing details for this decision.
  * Can include investigation notes, references to internal case IDs, and any additional context that may be useful for audit or downstream teams.
* `reviewedBy`
  * Required
  * Name or identifier of the person making the decision.
  * Example: `"Jane Doe"` or `"RiskUser123"`.

Example request body

```json
{
  "stakeholderEmail": "owner@example-merchant.com",
  "status": "declined",
  "reason": "Unable to verify business identity",
  "description": "Multiple attempts to collect valid documents failed. Business registration records could not be matched to application details.",
  "reviewedBy": "Jane Doe"
}
```

### Example request

```http
POST /merchant-onboarding/app-abc123/feedback
Authorization: Bearer {access_token}
Content-Type: application/json
Accept: application/json

{
  "stakeholderEmail": "owner@example-merchant.com",
  "status": "fraud",
  "reason": "Confirmed stolen identity",
  "description": "Document review and external data sources confirmed identity theft. Do not reapply without manual override.",
  "reviewedBy": "Risk Analyst 1"
}
```

The endpoint responds with a success status (for example 200, 201, or 202) when the feedback has been accepted and applied. The exact success code may vary by environment.

### When to use Manual Risk Feedback

Manual Risk Feedback is typically used by internal teams, not directly by partner systems or merchants. Common scenarios include:

* Manual approval\
  Automated checks flagged the application for review. A risk or compliance analyst investigates and decides the merchant is acceptable. They send feedback with `status = "approved"` and a clear reason and description.
* Manual decline\
  A human reviewer identifies issues that require declining the application, such as mismatched documents, unverifiable ownership, or other risk concerns. They send feedback with `status = "declined"` and a clear explanation.
* Fraud classification\
  Investigation confirms that the application involves fraud, such as stolen identities, fabricated records, or synthetic identities. The reviewer sends feedback with `status = "fraud"` and details in `reason` and `description`. This may drive additional internal handling, such as blocking related accounts.

You can build internal tools that call this endpoint behind the scenes, so risk and compliance users interact with a UI while your backend issues the API call.

### Interaction with onboarding status and webhooks

Manual Risk Feedback works alongside the core onboarding status and webhook flows.

Typical behavior:

* When feedback is applied, the underlying risk engine and onboarding system update their view of the application.
* Status may change from a pending or review state to an outcome driven by the feedback (for example, approved, declined, or fraud).
* Webhooks for application events can notify your systems that a decision has been recorded, allowing you to update your own records or trigger downstream workflows.

Recommended approach:

* Use Get Status to see the current application state before applying feedback.
* After sending feedback, rely on webhooks or another status call to see the resulting state.
* Use the combination of status and feedback history for audit and analytics.

### Error handling

You should handle potential error responses from this endpoint in your integration.

Typical patterns:

* 400 Bad Request\
  The request body is missing required fields, has invalid values (for example, status not in the allowed list), or otherwise fails validation. Your tooling should validate required fields before sending.
* 401 Unauthorized\
  The access token is missing, expired, or invalid. Ensure you obtain and send a valid token in the Authorization header.
* 403 Forbidden\
  The caller is authenticated but not authorized to submit feedback for this application. This typically indicates a permissions or configuration issue.
* 404 Not Found\
  The `applicationId` does not match any existing onboarding application in the environment.
* 409 Conflict\
  The application is in a state where the requested feedback cannot be applied, or there is a conflicting update. Your system should surface these errors clearly so risk and compliance teams understand when feedback was not accepted.

Log error responses with the application id, HTTP status, and the error body (with sensitive data masked as appropriate) to support troubleshooting.

### Best practices

* Use clear, consistent reasons\
  Encourage reviewers to use standardized phrases in `reason` so that you can later group and analyze decisions. Free-form detail can go into `description`.
* Track reviewer identity\
  Always populate `reviewedBy` with a value that lets you identify who made the decision. This is important for audit and compliance.
* Keep stakeholderEmail aligned\
  Ensure `stakeholderEmail` matches the primary contact or signer associated with the application so you can correlate feedback with the correct person.
* Combine with internal case management\
  If you use internal case or ticket systems, include case identifiers in the `description` field so that you can move between systems easily.
* Avoid exposing this directly to external partners\
  Manual Risk Feedback is primarily an internal control. If partners need to see outcomes, surface results via your own APIs or UI based on onboarding status, not by exposing this endpoint directly.

By using Manual Risk Feedback in combination with onboarding status, webhooks, and your internal risk processes, you can maintain a clear, auditable record of human decisions in the merchant onboarding lifecycle.


# Sample Payload

These examples show current request shapes for onboarding application submission.

Use this page when you need a working reference for:

* creating a short onboarding application
* creating a full onboarding application
* configuring fees
* sending an application directly to the signer
* creating a Draft application when attachments are required

For most Sandbox testing, use `submitImmediately: true`. This creates the application and immediately sends it to the signer.

If you need to upload attachments before the signer receives the application, use `submitImmediately: false`, upload attachments while the application is in Draft status, then submit the Draft application for signature.

### Short application sample

The short application endpoint is:

```http
POST /merchant-onboarding/applications-short
```

Use this request when you want to start onboarding with basic merchant, signer, fee, and crypto environment details. The hosted onboarding flow collects the remaining merchant information from the signer.

{% code expandable="true" %}

```json
{
  "merchantName": "Northwind Coffee Roasters",
  "partnerId": "partner_123",
  "partnerMid": "northwind-2048",
  "signerFirstName": "Morgan",
  "signerLastName": "Reed",
  "signerEmail": "morgan.reed@example.com",
  "isSettlementOnly": false,
  "isCommissionMerchant": false,
  "feeInformation": {
    "achSettlementFixedFee": {
      "sellRate": 1.5,
      "isBilledByPartner": false
    },
    "settlementReturnFixedFee": {
      "sellRate": 15,
      "isBilledByPartner": false
    },
    "monthlyMaintenanceFee": {
      "sellRate": 0,
      "isBilledByPartner": false
    },
    "bitcoinFee": {
      "transactionRate": {
        "sellRate": 2.5,
        "isBilledByPartner": false
      },
      "perItemFee": {
        "sellRate": 0.25,
        "isBilledByPartner": false
      }
    },
    "stablecoinFee": {
      "transactionRate": {
        "sellRate": 2.5,
        "isBilledByPartner": false
      },
      "perItemFee": {
        "sellRate": 0.25,
        "isBilledByPartner": false
      }
    },
    "digitalWalletsFees": [
      {
        "tenderType": "paypal",
        "paymentLocationType": "inStore",
        "transactionRate": {
          "sellRate": 2.29,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.09,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "paypal",
        "paymentLocationType": "online",
        "transactionRate": {
          "sellRate": 3.6,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.49,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "venmo",
        "paymentLocationType": "inStore",
        "transactionRate": {
          "sellRate": 2.29,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.09,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "venmo",
        "paymentLocationType": "online",
        "transactionRate": {
          "sellRate": 3.6,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.49,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "klarna",
        "paymentLocationType": "inStore",
        "transactionRate": {
          "sellRate": 5.79,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.45,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      },
      {
        "tenderType": "klarna",
        "paymentLocationType": "online",
        "transactionRate": {
          "sellRate": 5.79,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.45,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      }
    ]
  },
  "cryptoEnvironment": "sandbox",
  "submitImmediately": true
}
```

{% endcode %}

### Full application sample

The full application endpoint is:

```http
POST /merchant-onboarding/applications
```

Use this request when you already have the merchant’s business, stakeholder, banking, and fee details and want to prefill the onboarding application.

In the full application flow, merchant details are sent inside `merchantData`. The application signer is identified inside `merchantData.stakeholders` by setting `isApplicationSigner` to `true`.

{% code expandable="true" %}

```json
{
  "submitImmediately": true,
  "merchantData": {
    "merchantName": "Northwind Coffee Roasters",
    "partnerId": "partner_123",
    "partnerExternalId": "ext-merchant-2048",
    "registeredName": "Northwind Coffee Roasters LLC",
    "dbaName": "Northwind Coffee Roasters",
    "federalTaxIdType": "ein",
    "federalTaxId": "123456789",
    "naicsCode": "722515",
    "merchantCategoryCode": "5499",
    "businessDescription": "Retail coffee roaster and cafe",
    "proofOfBusinessType": "businessLicense",
    "registrationDate": "2021-04-12",
    "legalEntityType": "llc",
    "operationalMonths": null,
    "grossAnnualRevenue": 1250000,
    "highestMonthlyVolume": 160000,
    "averageTicketSize": 32,
    "maxTicketSize": 500,
    "registeredLegalAddress": {
      "address1": "123 Harbor Street",
      "city": "Boston",
      "region": "MA",
      "country": "US",
      "postalCode": "02110"
    },
    "proofOfAddressType": "utilityBill",
    "businessMailingAddress": {
      "address1": "123 Harbor Street",
      "city": "Boston",
      "region": "MA",
      "country": "US",
      "postalCode": "02110"
    },
    "isLegalAddress": false,
    "isOperatingAddress": true,
    "isVirtualAddress": false,
    "locationBusinessType": "physical",
    "locationName": "Northwind Coffee Roasters",
    "businessWebsite": "https://northwind.example.com",
    "businessEmail": "ops@northwind.example.com",
    "businessPhone": "+15085550100",
    "mainPointOfContact": {
      "firstName": "Morgan",
      "lastName": "Reed",
      "email": "morgan.reed@example.com",
      "phoneNumber": "+15085550101"
    },
    "hasNoOwners": false,
    "stakeholders": [
      {
        "firstName": "Morgan",
        "lastName": "Reed",
        "dateOfBirth": "1987-06-15T00:00:00Z",
        "citizenship": "US",
        "ownershipPercentage": 100,
        "ownershipType": "direct",
        "idType": "passport",
        "idNumber": "123456789",
        "countryOfIssuance": "US",
        "taxIdType": "ssn",
        "taxIdNumber": "123456789",
        "proofOfIdentityType": "passport",
        "proofOfAddressType": "utilityBill",
        "address": {
          "address1": "123 Harbor Street",
          "city": "Boston",
          "region": "MA",
          "country": "US",
          "postalCode": "02110"
        },
        "email": "morgan.reed@example.com",
        "phoneNumber": "+15085550101",
        "jobTitle": "Owner",
        "isApplicationSigner": true
      }
    ],
    "bankName": "First Harbor Bank",
    "routingNumber": "011000015",
    "accountNumber": "1234567890",
    "accountType": "checking",
    "proofOfBankAccountType": "bankLetter",
    "bankDepositMethod": "ach",
    "bankAddress": {
      "address1": "10 Bank Plaza",
      "city": "Boston",
      "region": "MA",
      "country": "US",
      "postalCode": "02111"
    },
    "feeInformation": {
      "achSettlementFixedFee": {
        "sellRate": 1.5,
        "isBilledByPartner": false
      },
      "settlementReturnFixedFee": {
        "sellRate": 15,
        "isBilledByPartner": false
      },
      "monthlyMaintenanceFee": {
        "sellRate": 0,
        "isBilledByPartner": false
      },
      "bitcoinFee": {
        "transactionRate": {
          "sellRate": 2.5,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.25,
          "isBilledByPartner": false
        }
      },
      "stablecoinFee": {
        "transactionRate": {
          "sellRate": 2.5,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0.25,
          "isBilledByPartner": false
        }
      },
      "digitalWalletsFees": [
        {
          "tenderType": "paypal",
          "paymentLocationType": "inStore",
          "transactionRate": {
            "sellRate": 2.29,
            "isBilledByPartner": false
          },
          "perItemFee": {
            "sellRate": 0.09,
            "isBilledByPartner": false
          },
          "refundFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          },
          "chargebackFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          }
        },
        {
          "tenderType": "paypal",
          "paymentLocationType": "online",
          "transactionRate": {
            "sellRate": 3.6,
            "isBilledByPartner": false
          },
          "perItemFee": {
            "sellRate": 0.49,
            "isBilledByPartner": false
          },
          "refundFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          },
          "chargebackFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          }
        },
        {
          "tenderType": "venmo",
          "paymentLocationType": "inStore",
          "transactionRate": {
            "sellRate": 2.29,
            "isBilledByPartner": false
          },
          "perItemFee": {
            "sellRate": 0.09,
            "isBilledByPartner": false
          },
          "refundFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          },
          "chargebackFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          }
        },
        {
          "tenderType": "venmo",
          "paymentLocationType": "online",
          "transactionRate": {
            "sellRate": 3.6,
            "isBilledByPartner": false
          },
          "perItemFee": {
            "sellRate": 0.49,
            "isBilledByPartner": false
          },
          "refundFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          },
          "chargebackFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          }
        },
        {
          "tenderType": "klarna",
          "paymentLocationType": "inStore",
          "transactionRate": {
            "sellRate": 5.79,
            "isBilledByPartner": false
          },
          "perItemFee": {
            "sellRate": 0.45,
            "isBilledByPartner": false
          },
          "refundFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          },
          "chargebackFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          }
        },
        {
          "tenderType": "klarna",
          "paymentLocationType": "online",
          "transactionRate": {
            "sellRate": 5.79,
            "isBilledByPartner": false
          },
          "perItemFee": {
            "sellRate": 0.45,
            "isBilledByPartner": false
          },
          "refundFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          },
          "chargebackFee": {
            "sellRate": 0,
            "isBilledByPartner": false
          }
        }
      ]
    },
    "cryptoEnvironment": "sandbox"
  }
}
```

{% endcode %}

### Draft application sample

Use `submitImmediately: false` only when the application must remain in Draft status before it is sent to the signer.

This is required when you need to upload attachments before the signer receives the application.

{% code expandable="true" %}

```json
{
  "submitImmediately": false,
  "merchantData": {
    "merchantName": "Northwind Coffee Roasters",
    "partnerId": "partner_123",
    "partnerExternalId": "ext-merchant-2048",
    "registeredName": "Northwind Coffee Roasters LLC",
    "stakeholders": [
      {
        "firstName": "Morgan",
        "lastName": "Reed",
        "email": "morgan.reed@example.com",
        "phoneNumber": "+15085550101",
        "jobTitle": "Owner",
        "ownershipPercentage": 100,
        "ownershipType": "direct",
        "isApplicationSigner": true
      }
    ],
    "feeInformation": {
      "achSettlementFixedFee": {
        "sellRate": 1.5,
        "isBilledByPartner": false
      },
      "settlementReturnFixedFee": {
        "sellRate": 15,
        "isBilledByPartner": false
      },
      "monthlyMaintenanceFee": {
        "sellRate": 0,
        "isBilledByPartner": false
      }
    },
    "cryptoEnvironment": "sandbox"
  }
}
```

{% endcode %}

After the Draft application is created, upload attachments while the application is still in Draft status.

```http
POST /merchant-onboarding/applications/{applicationId}/attachments
```

Then submit the Draft application for signature.

```http
POST /merchant-onboarding/applications/{applicationId}/submit
```

### Fee information reference

The `feeInformation` object defines the pricing values displayed to the signer during onboarding.

Fee values should be provided when creating applications that are sent directly to the signer. These values are rendered in the signing experience and should be reviewed before the application is submitted.

Each fee object includes:

| Field               | Description                                                         |
| ------------------- | ------------------------------------------------------------------- |
| `sellRate`          | The rate or fixed amount charged for the fee.                       |
| `isBilledByPartner` | Indicates whether the fee is billed by the partner instead of Bead. |

#### Settlement fees

```json
{
  "achSettlementFixedFee": {
    "sellRate": 1.5,
    "isBilledByPartner": false
  },
  "settlementReturnFixedFee": {
    "sellRate": 15,
    "isBilledByPartner": false
  },
  "monthlyMaintenanceFee": {
    "sellRate": 0,
    "isBilledByPartner": false
  }
}
```

Some partner configurations may also include additional settlement-related fees, such as wire settlement pricing, when applicable to the agreement and supported by the onboarding configuration.

#### Crypto fees

Use `bitcoinFee` for Bitcoin pricing and `stablecoinFee` for stablecoin pricing.

```json
{
  "bitcoinFee": {
    "transactionRate": {
      "sellRate": 2.5,
      "isBilledByPartner": false
    },
    "perItemFee": {
      "sellRate": 0.25,
      "isBilledByPartner": false
    }
  },
  "stablecoinFee": {
    "transactionRate": {
      "sellRate": 2.5,
      "isBilledByPartner": false
    },
    "perItemFee": {
      "sellRate": 0.25,
      "isBilledByPartner": false
    }
  }
}
```

#### Digital wallet fees

Use `digitalWalletsFees` for wallet and BNPL pricing.

Each digital wallet fee entry identifies:

| Field                 | Description                                               |
| --------------------- | --------------------------------------------------------- |
| `tenderType`          | The wallet or BNPL tender being priced.                   |
| `paymentLocationType` | Whether the pricing row applies to `inStore` or `online`. |
| `transactionRate`     | Percentage-style transaction rate.                        |
| `perItemFee`          | Fixed per-item fee.                                       |
| `refundFee`           | Fixed refund fee.                                         |
| `chargebackFee`       | Fixed chargeback fee.                                     |

For each wallet tender being priced, send both `inStore` and `online` rows. Bead may treat these values the same today for some tenders, but both values are accepted and reserved for future pricing separation. If your commercial terms do not distinguish online and in-store pricing, send the same values for both.

{% code expandable="true" %}

```json
{
  "digitalWalletsFees": [
    {
      "tenderType": "paypal",
      "paymentLocationType": "inStore",
      "transactionRate": {
        "sellRate": 2.29,
        "isBilledByPartner": false
      },
      "perItemFee": {
        "sellRate": 0.09,
        "isBilledByPartner": false
      },
      "refundFee": {
        "sellRate": 0,
        "isBilledByPartner": false
      },
      "chargebackFee": {
        "sellRate": 0,
        "isBilledByPartner": false
      }
    },
    {
      "tenderType": "paypal",
      "paymentLocationType": "online",
      "transactionRate": {
        "sellRate": 3.6,
        "isBilledByPartner": false
      },
      "perItemFee": {
        "sellRate": 0.49,
        "isBilledByPartner": false
      },
      "refundFee": {
        "sellRate": 0,
        "isBilledByPartner": false
      },
      "chargebackFee": {
        "sellRate": 0,
        "isBilledByPartner": false
      }
    }
  ]
}
```

{% endcode %}

### Example response

A successful application create response includes identifiers you should store for status tracking, troubleshooting, and follow-up commands.

```json
{
  "applicationId": "app-123exampleid",
  "envelopeId": "env-1111-2222-3333-4444",
  "status": "signing"
}
```

If the application was created with `submitImmediately: false`, the initial status may be `draft`.

```json
{
  "applicationId": "app-123exampleid",
  "envelopeId": "env-1111-2222-3333-4444",
  "status": "draft"
}
```

### Best practices

* Use the short application sample for fast Sandbox testing.
* Use the full application sample when your system already has most merchant details.
* Use `submitImmediately: true` for the simplest signer flow.
* Use `submitImmediately: false` only when attachments need to be uploaded before the signer receives the application.
* Store `applicationId` and `envelopeId` immediately after creation.
* Include `isBilledByPartner` on all fee objects.
* Use `bitcoinFee` and `stablecoinFee` for crypto fee configuration.
* Send both `inStore` and `online` rows for each digital wallet tender you are pricing.
* Confirm all fee values before sending the application to the signer.

### Related pages

* [Submit Application](/onboarding/submit-application)
* [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications)
* [Application Attachments](/onboarding/application-attachments)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)
* [Get Status](/onboarding/get-status)
* [Webhooks for Application Events](/onboarding/webhooks-for-application-events)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)


# Entity Management

The Entity Management API provides a unified interface for creating and managing the business objects that drive payments in Bead, including partners, merchants, locations, terminals, and related onboarding flows.

Partner → Merchant → Location → Terminal → Payment

It covers the full life cycle, from initial KYB onboarding through routine updates, so you can automate provisioning flows and build self-service dashboards.

Entity Management connects onboarding, provisioning, and ongoing administration across the Bead platform. Use it to move from initial merchant setup through operational management of merchants, locations, and terminals.

#### Why use Entity Management

**Need:** Onboard a new merchant

**How the API helps:** Submit KYB details, provide merchant and signer information, and generate a hosted onboarding link that the merchant signer can complete securely.

**Need:** Track onboarding progress

**How the API helps:** Query onboarding status to see missing or invalid fields and determine when a merchant is ready for production.

**Need:** Manage locations

**How the API helps:** Create, rename, or deactivate merchant locations so your reporting and settlement views reflect the merchant’s structure.

**Need:** Manage terminals

**How the API helps:** Create, list, update, disable, or delete devices and set device-level webhooks that drive payment status events.

#### Available sections

**Onboarding**\
Submit KYB details for merchants and partners, provide required applicant and signer information, generate hosted onboarding links, and track status through approval.

**Merchant Management**\
Create, read, update, and deactivate merchants once onboarding is complete. Check merchant status before enabling production traffic.

**Location Management**\
Create and maintain merchant locations, including display names and address details used in receipts, reporting, and settlement.

**Terminal Management**\
Provision and manage terminals, including webhooks and configuration that control how payments are initiated and where events are delivered.

#### Navigation

Use the left navigation to move between sections:

* [Onboarding](/onboarding)
* [Merchant Management](/entity-management/merchant-management)
* [Location Management](/entity-management/location-management)
* [Terminal Management](/entity-management/terminal-management)
* [Change Log](/changelog)

#### Getting started

1. **Confirm the authentication method for the API flow you are using**
   * Onboarding requests use an API key sent in the `X-Api-Key` header.
   * Other Entity Management endpoints may use the authentication model documented for that endpoint family.
   * See the Authentication page for current authentication guidance.
2. **Create or retrieve the parent objects in order**
   * Partner → Merchant → Location
3. **Provision terminals under the correct location**
   * Each terminal belongs to a location and merchant.
4. **Use the `terminalId` when creating payments**
   * Payments created through the Payments API reference the terminal and merchant defined here, then appear in Reporting and Settlement.


# Partner Management

Use Partner Management APIs to create and update partner records. Partner records sit above merchants in the Bead entity model and are used to organize merchant ownership, reporting, permissions, and hierarchy.

Most integrations do not need to create partner hierarchy records directly. Use these APIs only when Bead has confirmed that your integration should manage partner records programmatically.

#### Where Partner Management fits

For most integrations, the entity model is:

```
Partner → Merchant → Location → Terminal → Payment
```

For advanced ISO, reseller, or boarding-platform configurations, the partner level may include an expanded hierarchy:

```
ISO → Agent → Corporate Entity → Master Merchant → Merchant
```

For ISO, Agent, Corporate Entity, and Master Merchant behavior, see [Advanced Partner Hierarchy](/reference-guide/core-concepts/advanced-partner-hierarchy).

#### Authentication

All Partner Management endpoints use API key authentication.

| Header      | Value              |
| ----------- | ------------------ |
| `X-Api-Key` | `{adminApiKey}`    |
| `Accept`    | `application/json` |

For write operations, also include:

| Header         | Value              |
| -------------- | ------------------ |
| `Content-Type` | `application/json` |

#### List partners

Returns a paginated list of partner records. Use this endpoint to browse the partner hierarchy or locate a specific partner before creating or updating records.

```http
GET /Partners/all
```

**Query parameters**

| Parameter       | Type    | Required | Description                                                                 |
| --------------- | ------- | -------- | --------------------------------------------------------------------------- |
| `PartnerIds`    | array   | No       | Filter by specific partner IDs                                              |
| `IsEnabled`     | boolean | No       | Filter by enabled status                                                    |
| `Type`          | string  | No       | Filter by partner type: `iso`, `agent`, `corporateEntity`, `masterMerchant` |
| `Search`        | string  | No       | Search by name, email, or phone number                                      |
| `SortBy`        | string  | No       | One of: `Created`, `Updated`, `IsEnabled`, `PartnerType`, `Name`            |
| `SortDirection` | string  | No       | `asc` or `desc`                                                             |
| `Page`          | integer | No       | Page number, zero-indexed                                                   |
| `PageSize`      | integer | No       | Results per page, max `100`                                                 |

**Example request**

```bash
curl -X GET "https://api.test.devs.beadpay.io/Partners/all?Page=0&PageSize=50" \
     -H "X-Api-Key: {adminApiKey}"
```

**Response codes**

| Code | Condition                      |
| ---- | ------------------------------ |
| 200  | Partners returned successfully |
| 401  | Missing or invalid API key     |
| 403  | Not permitted to list partners |

#### Get a partner

Retrieves a specific partner record.

```http
GET /Partners/{id}
```

`id` is the ID of the partner to retrieve.

**Example request**

```bash
curl -X GET "https://api.test.devs.beadpay.io/Partners/{partnerId}" \
     -H "X-Api-Key: {adminApiKey}"
```

**Response codes**

| Code | Condition                            |
| ---- | ------------------------------------ |
| 200  | Partner returned successfully        |
| 401  | Missing or invalid API key           |
| 403  | Not permitted to access this partner |
| 404  | Partner not found                    |

#### Add a partner

Creates a child partner under an existing parent partner.

```http
POST /Partners/{parentId}/partners
```

`parentId` is the ID of the partner that should own the new partner record.

**Automatic downstream creation**

When a partner is added at the ISO, Agent, or Corporate Entity level, Bead automatically creates the downstream records required to reach a Master Merchant.

| Partner type added | Records Bead automatically creates       |
| ------------------ | ---------------------------------------- |
| `iso`              | Agent, Corporate Entity, Master Merchant |
| `agent`            | Corporate Entity, Master Merchant        |
| `corporateEntity`  | Master Merchant                          |
| `masterMerchant`   | None                                     |

Create the highest-level partner record Bead instructed you to create, then use the created hierarchy to identify the downstream Master Merchant record. Do not manually recreate the downstream hierarchy unless Bead has specifically instructed you to do so.

**Request body**

```json
{
  "partnerType": "iso",
  "name": "Acme Payments",
  "email": "operations@example.com",
  "phoneNumber": "+15555550100",
  "feeInfo": {
    "crypto": {
      "feeFactor": 0,
      "fixedFee": 0
    },
    "card": null
  },
  "tenderTypes": ["usdcBase", "bitcoin"],
  "maxTransactionAmount": 10000
}
```

In this example, adding `Acme Payments` as an ISO creates the required downstream Agent, Corporate Entity, and Master Merchant records automatically.

**Request fields**

| Field                  | Type   | Required | Description                                                            |
| ---------------------- | ------ | -------- | ---------------------------------------------------------------------- |
| `partnerType`          | string | Yes      | Type to create: `iso`, `agent`, `corporateEntity`, or `masterMerchant` |
| `name`                 | string | Yes      | Partner display name. Max 256 characters                               |
| `feeInfo`              | object | Yes      | Partner fee configuration                                              |
| `tenderTypes`          | array  | Yes      | Tender types enabled for the partner                                   |
| `email`                | string | No       | Partner contact email                                                  |
| `phoneNumber`          | string | No       | Partner contact phone number                                           |
| `maxTransactionAmount` | number | No       | Maximum transaction amount for this partner                            |

Note: `root` may appear as a `partnerType` value in read responses. It cannot be used when creating a partner.

**Example request**

```bash
curl -X POST "https://api.test.devs.beadpay.io/Partners/{parentId}/partners" \
     -H "X-Api-Key: {adminApiKey}" \
     -H "Content-Type: application/json" \
     -d '{
       "partnerType": "iso",
       "name": "Acme Payments",
       "email": "operations@example.com",
       "phoneNumber": "+15555550100",
       "feeInfo": {
         "crypto": { "feeFactor": 0, "fixedFee": 0 },
         "card": null
       },
       "tenderTypes": ["usdcBase", "bitcoin"],
       "maxTransactionAmount": 10000
     }'
```

**Response codes**

| Code | Condition                                           |
| ---- | --------------------------------------------------- |
| 200  | Partner created successfully                        |
| 201  | Partner created successfully                        |
| 202  | Creation accepted for processing                    |
| 400  | Invalid fields or validation error                  |
| 401  | Missing or invalid API key                          |
| 403  | Not permitted to create a partner under this parent |
| 409  | Conflict — duplicate or constraint violation        |

#### Add another Agent branch

If Bead has instructed you to create an additional Agent under an existing ISO, create the Agent as a child partner of the ISO. Bead automatically creates the Corporate Entity and Master Merchant records under that Agent branch.

```json
{
  "partnerType": "agent",
  "name": "Acme Payments Agent Office 1",
  "email": "operations@example.com",
  "phoneNumber": "+15555550100",
  "feeInfo": {
    "crypto": {
      "feeFactor": 0,
      "fixedFee": 0
    },
    "card": null
  },
  "tenderTypes": ["usdcBase", "bitcoin"],
  "maxTransactionAmount": 10000
}
```

#### Edit a partner

Updates an existing partner record.

```http
PUT /Partners/{id}
```

`id` is the ID of the partner to update.

**Request body**

```json
{
  "name": "Acme Payments Agent Office 1",
  "email": "operations@example.com",
  "phoneNumber": "+15555550100",
  "feeInfo": {
    "crypto": {
      "feeFactor": 0,
      "fixedFee": 0
    },
    "card": null
  },
  "tenderTypes": ["usdcBase", "bitcoin"],
  "maxTransactionAmount": 10000
}
```

**Request fields**

| Field                  | Type   | Required | Description                                 |
| ---------------------- | ------ | -------- | ------------------------------------------- |
| `name`                 | string | Yes      | Partner display name. Max 256 characters    |
| `feeInfo`              | object | Yes      | Partner fee configuration                   |
| `tenderTypes`          | array  | Yes      | Tender types enabled for the partner        |
| `email`                | string | No       | Partner contact email                       |
| `phoneNumber`          | string | No       | Partner contact phone number                |
| `maxTransactionAmount` | number | No       | Maximum transaction amount for this partner |

**Example request**

```bash
curl -X PUT "https://api.test.devs.beadpay.io/Partners/{partnerId}" \
     -H "X-Api-Key: {adminApiKey}" \
     -H "Content-Type: application/json" \
     -d '{
       "name": "Acme Payments Agent Office 1",
       "email": "operations@example.com",
       "phoneNumber": "+15555550100",
       "feeInfo": {
         "crypto": { "feeFactor": 0, "fixedFee": 0 },
         "card": null
       },
       "tenderTypes": ["usdcBase", "bitcoin"],
       "maxTransactionAmount": 10000
     }'
```

**Response codes**

| Code | Condition                                    |
| ---- | -------------------------------------------- |
| 200  | Partner updated successfully                 |
| 202  | Update accepted for processing               |
| 204  | Update completed, no response body           |
| 400  | Invalid fields or validation error           |
| 401  | Missing or invalid API key                   |
| 403  | Not permitted to update this partner         |
| 404  | Partner not found                            |
| 409  | Conflict — duplicate or constraint violation |

#### Delete a partner

Removes a partner record.

```http
DELETE /Partners/{id}
```

`id` is the ID of the partner to delete.

**Example request**

```bash
curl -X DELETE "https://api.test.devs.beadpay.io/Partners/{partnerId}" \
     -H "X-Api-Key: {adminApiKey}"
```

**Response codes**

| Code | Condition                                  |
| ---- | ------------------------------------------ |
| 204  | Partner deleted successfully               |
| 400  | Invalid request or business rule violation |
| 401  | Missing or invalid API key                 |
| 403  | Not permitted to delete this partner       |
| 404  | Partner not found                          |

#### Notes

Partner creation and updates can affect downstream merchant ownership, reporting, permissions, tender availability, and which Master Merchant a merchant should be boarded under. Before adding or editing partner records, confirm the intended parent partner ID and target hierarchy branch with Bead.

#### Related pages

* [Advanced Partner Hierarchy](/reference-guide/core-concepts/advanced-partner-hierarchy)
* [Merchant Management](/entity-management/merchant-management)
* [List Merchants](/entity-management/merchant-management/list-merchants)


# Merchant Management

Use the Merchant Management API to retrieve and update merchant records after KYB approval. Check merchant status before enabling production traffic, and use the membership endpoints to manage user access to a merchant account.

#### Where Merchant Management fits

Merchants sit below partners and above locations in the Bead entity hierarchy:

```
Partner → Merchant → Location → Terminal → Payment
```

A merchant record is created during the onboarding and KYB process. Once approved, use these endpoints to read and maintain the record, manage locations, and control user access.

#### Endpoints at a glance

| Action          | Method and path                                 |
| --------------- | ----------------------------------------------- |
| Get merchant    | `GET /Merchants/{id}`                           |
| Update merchant | `PUT /Merchants/{id}`                           |
| List merchants  | `GET /Partners/{id}/merchants`                  |
| List locations  | `GET /Merchants/{id}/locations`                 |
| List terminals  | `GET /Merchants/{id}/terminals`                 |
| List members    | `GET /Merchants/{id}/members`                   |
| Add member      | `POST /Merchants/{id}/members`                  |
| Remove member   | `DELETE /Merchants/{id}/members/{userIssuerId}` |

#### Merchant status

Before enabling production payment traffic, confirm the merchant's `status` field. A merchant must be `active` to process payments.

| Status       | Meaning                               |
| ------------ | ------------------------------------- |
| `unverified` | KYB not yet completed                 |
| `active`     | Approved and able to process payments |
| `locked`     | Temporarily restricted by Bead        |
| `restricted` | Operating under specific limitations  |
| `inactive`   | Not currently processing              |
| `suspended`  | Suspended pending review              |
| `terminated` | Permanently terminated                |
| `closed`     | Account closed                        |

#### Related pages

* [Get Merchant](/entity-management/merchant-management/get-merchant)
* [Location Management](/entity-management/location-management)
* [Terminal Management](/entity-management/terminal-management)


# Get Merchant

Retrieves the complete profile of a single merchant, including company details, tender types, fee settings, and membership list.

### Endpoint

```
GET /Merchants/{id}
```

### Authentication

```
X-Api-Key: {adminApiKey}
```

### Path parameters

<table><thead><tr><th width="113">Name</th><th width="131">Type</th><th width="153">Required</th><th>Description</th></tr></thead><tbody><tr><td>id</td><td>string</td><td>Yes</td><td>The merchantId assigned during boarding</td></tr></tbody></table>

### Request headers

<table><thead><tr><th width="189">Header</th><th>Value</th></tr></thead><tbody><tr><td>X-Api-Key</td><td>{adminApiKey}</td></tr><tr><td>Accept</td><td>application/json</td></tr></tbody></table>

### Example request

```
curl -X GET "https://api.test.devs.beadpay.io/Merchants/{merchantId}" \
-H "X-Api-Key: {adminApiKey}"
```

### Successful response — 200

The response wraps the merchant record in two parts: `merchant` (core record) and `details` (extended KYB details, or `null` if not available). This is the same shape returned by [List Merchants](/entity-management/merchant-management/list-merchants) at `data[].merchant` / `data[].details`.

{% code expandable="true" %}

```json
{
  "merchant": {
    "displayId": "M001667",
    "partnerExternalId": null,
    "partnerId": "690e2895852fff2eff800c68",
    "companyName": "Acme Coffee Roasters",
    "description": "West-coast café chain",
    "taxNumberType": "ein",
    "taxNumber": "99-9999999",
    "companyLogoPath": null,
    "isSettlementOnly": false,
    "isCommissionMerchant": false,
    "commissionMerchantId": null,
    "status": "active",
    "feeInformation": {
      "achSettlementFixedFee": { "sellRate": 0.5, "isBilledByPartner": false },
      "settlementReturnFixedFee": { "sellRate": 25.0, "isBilledByPartner": false },
      "monthlyMaintenanceFee": null,
      "cryptoFees": [
        {
          "tenderType": "bitcoin",
          "transactionRate": { "sellRate": 3.5, "isBilledByPartner": false },
          "perItemFee": { "sellRate": 0.25, "isBilledByPartner": false }
        },
        {
          "tenderType": "bitcoin_Lightning",
          "transactionRate": { "sellRate": 3.5, "isBilledByPartner": false },
          "perItemFee": { "sellRate": 0.25, "isBilledByPartner": false }
        },
        {
          "tenderType": "usdcBase",
          "transactionRate": { "sellRate": 3.5, "isBilledByPartner": false },
          "perItemFee": { "sellRate": 0.25, "isBilledByPartner": false }
        },
        {
          "tenderType": "usdcSolana",
          "transactionRate": { "sellRate": 3.5, "isBilledByPartner": false },
          "perItemFee": { "sellRate": 0.25, "isBilledByPartner": false }
        }
      ],
      "digitalWalletsFees": [
        {
          "tenderType": "cashApp",
          "transactionRate": { "sellRate": 0.0, "isBilledByPartner": false },
          "perItemFee": { "sellRate": 0.0, "isBilledByPartner": false },
          "refundFee": { "sellRate": 0.0, "isBilledByPartner": false },
          "chargebackFee": { "sellRate": 0.0, "isBilledByPartner": false }
        }
      ]
    },
    "maxTransactionAmount": null,
    "memberships": [],
    "representsPartnerId": null,
    "isPinned": false,
    "receivableCurrencyIds": [1],
    "feeInfo": null,
    "settlementBeneficiary": { "type": "self", "partnerId": "690e2895852fff2eff800c68" },
    "tenderTypes": ["usdcBase", "usdcSolana", "bitcoin", "bitcoin_Lightning"],
    "id": "697109947a7f738950a8428d",
    "created": "2026-01-21T17:15:00Z",
    "updated": "2026-05-08T18:40:01.925Z"
  },
  "details": {
    "legalName": "Acme Coffee Roasters, Inc.",
    "dbaName": "Acme Coffee Roasters",
    "registrationDate": "2025-12-04",
    "legalEntityType": "corporationSType",
    "naicsCode": "541511",
    "merchantCategoryCode": "5734",
    "isSeasonal": false,
    "grossAnnualRevenue": 100000.0,
    "highestMonthlyVolume": 12000.0,
    "averageTicketSize": 200.0,
    "maxTicketSize": 10000.0,
    "businessWebsite": "https://acmecoffee.example.com/",
    "businessEmail": "billing@acmecoffee.example.com",
    "businessPhone": "+12255550100",
    "mailingAddress": {
      "address1": "1800 City Farm Dr",
      "address2": "Bldg 1B",
      "city": "Baton Rouge",
      "region": "LA",
      "country": "US",
      "postalCode": "70806"
    },
    "proofOfBusinessType": "articlesOfIncorporation",
    "proofOfAddressType": null,
    "mainPointOfContact": {
      "lastName": "Roaster",
      "firstName": "Ada",
      "email": "billing@acmecoffee.example.com",
      "phoneNumber": "+12255550101"
    },
    "bankInfo": {
      "bankName": "Example Bank",
      "accountNumber": "****1234",
      "accountType": "checking",
      "routingNumber": "****5678",
      "depositMethod": "ach"
    },
    "id": "697109947a7f738950a8428d",
    "created": "2026-01-21T17:15:00Z",
    "updated": "2026-05-08T18:40:02.237Z"
  }
}
```

{% endcode %}

`receivableCurrencyIds` contains integer IDs from the `/currencies` endpoint. `1` represents USD.

This is a live example (redacted). `feeInfo` came back `null` here — see the callout below for what that means.

### Key fields

<table><thead><tr><th width="278">Field</th><th>Description</th></tr></thead><tbody><tr><td>merchant</td><td>Core merchant record</td></tr><tr><td>details</td><td>Extended KYB details, or <code>null</code> if not available</td></tr><tr><td>tenderTypes</td><td>Default rails every child location inherits unless overridden at the location or terminal</td></tr><tr><td>receivableCurrencyIds</td><td>Settlement currencies, matched against IDs from <code>/currencies</code></td></tr><tr><td>feeInfo</td><td>Nullable. Observed as <code>null</code> on merchants; carries the same <code>crypto</code>/<code>card</code> flat-rate shape used when creating or updating a partner. Not the field to read for per-asset crypto pricing</td></tr><tr><td>feeInformation.cryptoFees</td><td>Per-asset crypto fee breakdown — one entry per <code>tenderType</code> (e.g. <code>bitcoin</code>, <code>bitcoin_Lightning</code>, <code>usdcBase</code>, <code>usdcSolana</code>). This is the field to read for the rate on a specific crypto asset</td></tr><tr><td>feeInformation.digitalWalletsFees</td><td>Per-tender-type fees for wallet rails (Cash App, PayPal, Venmo, etc.), same shape as <code>cryptoFees</code>, or <code>null</code> if none configured</td></tr><tr><td>status</td><td>Boarding state: <code>unverified</code>, <code>active</code>, <code>locked</code>, <code>restricted</code>, <code>inactive</code>, <code>suspended</code>, <code>terminated</code>, <code>closed</code></td></tr><tr><td>memberships</td><td>Users and roles that manage this merchant in the dashboard</td></tr></tbody></table>

\<details> \<summary>feeInfo vs. feeInformation.cryptoFees\</summary>

`feeInfo` and `feeInformation` are both live fields in the API — `feeInfo` is not deprecated. In practice, `feeInfo` is returned as `null` on merchants; the same `feeInfo` shape (`crypto`/`card`, each with a single `feeFactor`/`fixedFee`) is used as required input on partner creation and update, so it most likely reflects partner-level terms rather than something set per merchant. `feeInformation.cryptoFees` is the field that's populated on merchants and carries the actual per-`tenderType` breakdown — each entry has its own `transactionRate` (percentage) and `perItemFee` (fixed). If you need to know what a merchant is charged for bitcoin versus USDC specifically, read `feeInformation.cryptoFees`.

\</details>

### Error responses

<table><thead><tr><th width="90">Code</th><th>Condition</th></tr></thead><tbody><tr><td>401</td><td>Missing or invalid API key</td></tr><tr><td>403</td><td>Authenticated but not permitted to access this merchant</td></tr><tr><td>404</td><td>Merchant {id} does not exist or is not visible to your key</td></tr></tbody></table>

### Best practices

* Cache the response for dashboard sessions — merchant profiles rarely change during a user session
* Use the `updated` timestamp to decide when to invalidate local copies
* Check `status` before enabling production payment traffic — the merchant must be `active`
* Respect `maxTransactionAmount` when building POS flows to prevent declines on oversize transactions
* Read `feeInformation.cryptoFees` for the fee on a specific crypto asset — `feeInfo` is typically `null` on merchants

### Related pages

* [Merchant Management](/entity-management/merchant-management)
* [List Locations](/entity-management/location-management/list-locations)
* [Create Location](/entity-management/location-management/create-location)


# List Merchants

Returns a paginated list of merchants under a specific partner. This endpoint is only available for partners at the Master Merchant level.

#### Endpoint

`GET /Partners/{id}/merchants`

#### Authentication

```http
X-Api-Key: {adminApiKey}
```

#### Path parameters

| Name | Type   | Required | Description                                                                     |
| ---- | ------ | -------- | ------------------------------------------------------------------------------- |
| `id` | string | Yes      | The `partnerId` of the Master Merchant partner whose merchants you want to list |

#### Query parameters

| Parameter       | Type    | Required | Description                                                                                                   |
| --------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `Status`        | string  | No       | Filter by merchant status. See [Merchant Management](/entity-management/merchant-management) for valid values |
| `Search`        | string  | No       | Search by company name                                                                                        |
| `SortBy`        | string  | No       | Field to sort by. Supported values: `Created`, `Updated`, `CompanyName`                                       |
| `SortDirection` | string  | No       | `asc` or `desc`                                                                                               |
| `Page`          | integer | No       | Page number, zero-indexed. Defaults to `0`                                                                    |
| `PageSize`      | integer | No       | Number of results per page. Defaults to `50`, max `100`                                                       |

#### Request headers

| Header      | Value              |
| ----------- | ------------------ |
| `X-Api-Key` | `{adminApiKey}`    |
| `Accept`    | `application/json` |

#### Example request

```bash
curl -X GET "https://api.test.devs.beadpay.io/Partners/{partnerId}/merchants?Page=0&PageSize=50" \
     -H "X-Api-Key: {adminApiKey}"
```

#### Successful response — 200

The response is a paginated wrapper containing an array of merchant objects.

```json
{
  "data": [
    {
      "merchant": {
        "id": "mer_123123",
        "partnerId": "part_e9ab6c82b6d74a4f9c33",
        "companyName": "Acme Coffee Roasters",
        "status": "active",
        "tenderTypes": ["ethereum", "usdcBase"],
        "receivableCurrencyIds": [1],
        "isSettlementOnly": false,
        "isCommissionMerchant": false,
        "created": "2025-06-04T18:41:14.402Z",
        "updated": "2025-06-04T18:41:14.402Z"
      },
      "details": null
    }
  ],
  "total": 1,
  "page": 0
}
```

#### Response fields

| Field             | Description                                                                                                             |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `data`            | Array of merchant objects                                                                                               |
| `data[].merchant` | Core merchant record — see [Get Merchant](/entity-management/merchant-management/get-merchant) for full field reference |
| `data[].details`  | Extended KYB details object, or `null` if not available                                                                 |
| `total`           | Total number of merchants matching the query                                                                            |
| `page`            | Current page number, zero-indexed                                                                                       |

#### Error responses

| Code | Condition                                                                                               |
| ---- | ------------------------------------------------------------------------------------------------------- |
| 401  | Missing or invalid API key                                                                              |
| 403  | Authenticated but not permitted to list merchants for this partner, or partner is not a Master Merchant |
| 404  | Partner `{id}` does not exist or is not visible to your key                                             |

#### Best practices

* Confirm the partner is at the Master Merchant level before calling this endpoint — other partner types will return a 403
* Use `Status=active` to filter to merchants ready for production traffic
* Use `Search` to locate a specific merchant by company name rather than fetching all pages
* Store `total` to drive pagination logic in your UI

#### Related pages

* [Get Merchant](/entity-management/merchant-management/get-merchant)
* [Merchant Management](/entity-management/merchant-management)
* [Partner Management](/entity-management/partner-management)


# Update Merchant

Use these endpoints to submit and track requests to update a merchant record after KYB approval. Update requests go through an approval workflow before changes are applied, and support scheduled and time-limited updates.

### How update requests work

An update request captures a proposed change to a merchant record. Rather than applying changes directly, the request enters an approval queue. Once approved, Bead applies the changes automatically. If `effectiveAt` is set, the change applies at the scheduled time. If `expiresAt` is set, the change is reverted when that time passes.

This workflow is appropriate for changes to merchant details, fee configuration, merchant status, pinned status, or removal requests.

### Endpoints

```
POST /Merchants/{merchantId}/update-requests
GET  /Merchants/{merchantId}/update-requests
```

***

### Create Update Request

Submits a new update request for a merchant. Set `submit: true` to enter the request into the approval queue immediately. Set `submit: false` to create the request in DRAFT status for later review or submission.

#### Authentication

```
X-Api-Key: {adminApiKey}
```

#### Path parameter

| Name       | Type   | Required | Description                       |
| ---------- | ------ | -------- | --------------------------------- |
| merchantId | string | Yes      | The ID of the merchant to update. |

#### Request body

| Field           | Type                 | Required | Description                                                                                                                   |
| --------------- | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| submit          | boolean              | Yes      | If true, the request is submitted for approval immediately. If false, the request is created in DRAFT status.                 |
| precedence      | integer              | Yes      | Priority level used when multiple update requests have overlapping time windows. Higher values take priority. Default is 100. |
| changes         | object               | Yes      | The changes to apply to the merchant. See changes object below.                                                               |
| clientReference | string               | No       | Optional reference string for external tracking.                                                                              |
| effectiveAt     | string (RFC3339 UTC) | No       | When the changes should take effect. If omitted, changes apply immediately upon approval.                                     |
| expiresAt       | string (RFC3339 UTC) | No       | When the changes should expire and be reverted. Optional.                                                                     |

#### Changes object

| Field           | Type    | Description                                                                                |
| --------------- | ------- | ------------------------------------------------------------------------------------------ |
| merchantDetails | object  | Updated merchant profile fields such as company name, description, or contact information. |
| fee             | object  | Updated fee configuration for the merchant.                                                |
| status          | string  | Updated merchant status. Accepts a valid merchant status value.                            |
| setPinned       | boolean | Sets the pinned status of the merchant account.                                            |
| requestRemoval  | boolean | Requests soft deletion of the merchant and its terminals and locations.                    |
| removalReason   | string  | Reason for the removal request. Required when requestRemoval is true.                      |

#### Example request

```bash
curl -X POST "https://api.test.devs.beadpay.io/Merchants/{merchantId}/update-requests" \
  -H "X-Api-Key: {adminApiKey}" \
  -H "Content-Type: application/json" \
  -d '{
    "submit": true,
    "precedence": 100,
    "clientReference": "your-internal-ref-001",
    "effectiveAt": null,
    "expiresAt": null,
    "changes": {
      "merchantDetails": {
        "companyName": "Acme Coffee Roasters Inc"
      }
    }
  }'
```

#### Successful response — 200

```json
{
  "requestId": "ureq_abc123",
  "merchantId": "mer_123123",
  "clientReference": "your-internal-ref-001",
  "status": "submitted",
  "requesterId": "user_ab12",
  "createdAt": "2025-07-01T12:00:00Z"
}
```

#### Response fields

| Field           | Description                                                                  |
| --------------- | ---------------------------------------------------------------------------- |
| requestId       | The unique ID of the update request. Store this to track the request status. |
| merchantId      | The ID of the merchant this request applies to.                              |
| clientReference | The client reference you supplied, if any.                                   |
| status          | The current status of the request. See status lifecycle below.               |
| requesterId     | The ID of the user who created the request.                                  |
| createdAt       | Timestamp when the request was created.                                      |

#### Request status lifecycle

| Status    | Meaning                                                                             |
| --------- | ----------------------------------------------------------------------------------- |
| draft     | The request was created with submit: false and has not been submitted for approval. |
| submitted | The request is in the approval queue.                                               |
| approved  | The request has been approved and is pending application.                           |
| applied   | The changes have been applied to the merchant record.                               |
| rejected  | The request was not approved. No changes were applied.                              |
| expired   | The request reached its expiresAt time and was reverted or not applied.             |
| cancelled | The request was cancelled before approval or application.                           |

#### Error responses

| Code | Condition                                                                    |
| ---- | ---------------------------------------------------------------------------- |
| 400  | Request body is missing required fields or contains invalid values.          |
| 401  | Missing or invalid API key.                                                  |
| 403  | Authenticated but not permitted to submit update requests for this merchant. |
| 404  | Merchant does not exist or is not visible to your key.                       |

### List Update Requests

Returns a paginated list of update requests for a merchant, with optional filters by status, date range, and search term.

#### Authentication

```
X-Api-Key: {adminApiKey}
```

#### Path parameter

| Name       | Type   | Required | Description                                               |
| ---------- | ------ | -------- | --------------------------------------------------------- |
| merchantId | string | Yes      | The ID of the merchant whose update requests to retrieve. |

#### Query parameters

| Parameter | Type             | Description                                                                           |
| --------- | ---------------- | ------------------------------------------------------------------------------------- |
| Statuses  | array of strings | Filter by one or more request statuses. Returns the union across all supplied values. |
| Search    | string           | Free text search across merchant name, request ID, and merchant display ID (MID).     |
| From      | string           | Filter by requests submitted on or after this date.                                   |
| To        | string           | Filter by requests submitted on or before this date.                                  |
| Page      | integer          | Page number. Starts at 1.                                                             |
| PageSize  | integer          | Number of results per page.                                                           |

#### Example request

```bash
curl -X GET "https://api.test.devs.beadpay.io/Merchants/{merchantId}/update-requests?Statuses=submitted&Statuses=approved&Page=1&PageSize=25" \
  -H "X-Api-Key: {adminApiKey}"
```

#### Successful response — 200

```json
{
  "data": [
    {
      "requestId": "ureq_abc123",
      "merchantId": "mer_123123",
      "clientReference": "your-internal-ref-001",
      "status": "submitted",
      "requesterId": "user_ab12",
      "createdAt": "2025-07-01T12:00:00Z"
    }
  ],
  "total": 1,
  "page": 1
}
```

#### Response fields

| Field | Description                                                                                |
| ----- | ------------------------------------------------------------------------------------------ |
| data  | Array of update request objects. Each object follows the same schema as the POST response. |
| total | Total number of matching requests across all pages.                                        |
| page  | The current page number returned.                                                          |

#### Error responses

| Code | Condition                                                                  |
| ---- | -------------------------------------------------------------------------- |
| 401  | Missing or invalid API key.                                                |
| 403  | Authenticated but not permitted to view update requests for this merchant. |
| 404  | Merchant does not exist or is not visible to your key.                     |

### Best practices

Store `requestId` when creating a request so you can look it up or reference it later.

Use `clientReference` to tie a Bead update request back to an action or record in your own system.

Use `submit: false` when you want to stage a change for review before it enters the approval queue.

Use `effectiveAt` when a change needs to take effect at a specific future time rather than immediately upon approval.

Use `expiresAt` for temporary changes that should be automatically reverted.

Use the `Statuses` filter when listing requests to focus on requests that are pending action such as submitted or approved.

Check `status` after submitting to confirm whether the request entered the approval queue or requires additional action.

### Related pages

* [Merchant Management](/entity-management/merchant-management)
* [Get Merchant](/entity-management/merchant-management/get-merchant)
* [List Merchants](/entity-management/merchant-management/list-merchants)


# Location Management

A location represents a single store, branch, or business unit that belongs to a merchant. Every terminal, payment, and report ties back to a location, so creating it correctly keeps settlement and analytics clean.

### Lifecycle

<table><thead><tr><th width="121">Phase</th><th>Typical call</th><th>What happens</th></tr></thead><tbody><tr><td>Provision</td><td><code>POST /Merchants/{id}/locations</code></td><td>Returns a <code>locationId</code> used when creating terminals or pulling reporting data</td></tr><tr><td>Configure</td><td><code>PUT /Locations/{id}</code></td><td>Replace all editable fields including name, tenderTypes, business type, MCC values, ticket sizes, and address</td></tr><tr><td>Disable</td><td><code>PUT /Locations/{id}/enabled</code></td><td>Stops new payment activity while preserving location history for reporting and settlement</td></tr><tr><td>Operate</td><td>Use <code>locationId</code> across terminal creation, reporting, and reconciliation</td><td>Dashboards and settlement reports group transactions by location</td></tr><tr><td>Retire</td><td><code>DELETE /Locations/{id}</code> once no active terminals remain</td><td>Permanently removes the location record</td></tr></tbody></table>

### Hierarchy recap

```
Partner
└─ Merchant
   └─ Location
      └─ Terminal
         └─ Payment
```

### Endpoints at a glance

<table><thead><tr><th width="203">Action</th><th>Method and path</th></tr></thead><tbody><tr><td>Create location</td><td><code>POST /Merchants/{id}/locations</code></td></tr><tr><td>List locations</td><td><code>GET /Merchants/{id}/locations</code></td></tr><tr><td>Get location</td><td><code>GET /Locations/{id}</code></td></tr><tr><td>Update location</td><td><code>PUT /Locations/{id}</code></td></tr><tr><td>Disable location</td><td><code>PUT /Locations/{id}/enabled</code></td></tr><tr><td>Delete location</td><td><code>DELETE /Locations/{id}</code></td></tr></tbody></table>

### Business types

The `locationBusinessType` field controls which payment connections are provisioned for the merchant during onboarding. **Set this value carefully — it cannot be changed after boarding without a full application resubmission.**

| Value      | Description                                            | Payment connections provisioned                                                                                                                                |
| ---------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `physical` | Brick-and-mortar store or branch                       | Physical connections only. Virtual wallet connections (Klarna, USDC, Lightning, etc.) are not built. Virtual terminals using wallet or BNPL tenders will fail. |
| `virtual`  | Online or remote business. Requires a `website` value. | Virtual wallet connections only. Physical terminal connections are not built.                                                                                  |
| `both`     | Operates both physically and online                    | Both physical and virtual connections are provisioned. Supports all terminal types and tender configurations.                                                  |

> **Important:** If a merchant is boarded with `locationBusinessType: "physical"` and later needs to accept virtual wallet payments, the application must be resubmitted with `"virtual"` or `"both"`. There is no post-boarding maintenance path to add connection types.

### Best practices

**Create under the correct merchant.** Pass the correct `merchantId` so dashboards roll up by store.

**Set the right business type before submission.** The `locationBusinessType` value determines which payment connections are built at boarding time. If your merchant needs to accept virtual wallet tenders (Klarna, USDC, crypto, etc.), use `"virtual"` or `"both"`. A `"physical"`-only boarding cannot support Virtual terminals.

**Disable before delete.** Use `PUT /Locations/{id}/enabled` to stop new payments before decommissioning a location.

**Use consistent naming.** A clear pattern such as city and store number helps support teams locate a store quickly.

**Tender type inheritance.** A terminal inherits `tenderTypes` from its parent location unless overridden, and a location inherits merchant defaults if `tenderTypes` is omitted at creation.

### Related pages

* [Create Location](/entity-management/location-management/create-location)
* [List Locations](/entity-management/location-management/list-locations)
* [Get Location](/entity-management/location-management/get-location)
* [Update Location](/entity-management/location-management/update-location)
* [Delete Location](/entity-management/location-management/delete-location)


# Create Location

Creates a new store or branch location under an existing merchant. The call returns a `locationId` that you will reference when you create terminals or pull reporting data.

#### Endpoint

`POST /Merchants/{id}/locations`

#### Path parameters

| Name | Type   | Required | Description                                     |
| ---- | ------ | -------- | ----------------------------------------------- |
| `id` | string | Yes      | The `merchantId` that will own the new location |

#### Request headers

| Header         | Value              |
| -------------- | ------------------ |
| `X-Api-Key`    | `{adminApiKey}`    |
| `Content-Type` | `application/json` |
| `Accept`       | `application/json` |

#### Request body

| Field                             | Type           | Required | Description                                                                             |
| --------------------------------- | -------------- | -------- | --------------------------------------------------------------------------------------- |
| `name`                            | string         | Yes      | Display name for the location                                                           |
| `address`                         | object         | Yes      | See Address object below                                                                |
| `businessType`                    | string         | Yes      | `physical`, `virtual`, or `both`                                                        |
| `merchantCategoryCode`            | string         | Yes      | Four digit MCC such as `5812`                                                           |
| `additionalMerchantCategoryCodes` | array\<string> | Yes      | Can be an empty array                                                                   |
| `descriptionOfServices`           | string         | Yes      | What the merchant sells or provides                                                     |
| `grossAnnualVolume`               | number         | Yes      | Estimated annual processing volume                                                      |
| `averageTicketSize`               | number         | Yes      | Typical transaction amount                                                              |
| `maximumTicketSize`               | number         | Yes      | Maximum expected transaction amount                                                     |
| `contactPhone`                    | string         | Yes      | E.164 or local format accepted                                                          |
| `contactEmail`                    | string         | Yes      | Contact inbox for the location                                                          |
| `website`                         | string or null | Yes      | Required when `businessType` is `virtual` or `both`. Send `null` for physical locations |
| `tenderTypes`                     | array\<string> | No       | Tender types for this location. Omit to inherit merchant defaults                       |
| `externalId`                      | string or null | No       | Optional external reference for your system                                             |
| `highestMonthlyVolume`            | number or null | No       | Peak expected monthly volume if known                                                   |

Note: `maxTransactionAmount` is not used at the location level. Use `averageTicketSize` and `maximumTicketSize` instead.

**Address object**

| Field        | Type   | Required | Description                     |
| ------------ | ------ | -------- | ------------------------------- |
| `address1`   | string | Yes      | Street line one                 |
| `address2`   | string | No       | Suite, floor, unit              |
| `city`       | string | Yes      | City or locality                |
| `region`     | string | Yes      | State or province such as `MA`  |
| `country`    | string | Yes      | ISO 3166-1 alpha-2 such as `US` |
| `postalCode` | string | Yes      | ZIP or postal code              |

#### Example request

```bash
curl -X POST "https://api.test.devs.beadpay.io/Merchants/{merchantId}/locations" \
  -H "X-Api-Key: {adminApiKey}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Downtown Flagship",
    "address": {
      "address1": "123 Main St",
      "address2": "Suite 200",
      "city": "Springfield",
      "region": "MA",
      "country": "US",
      "postalCode": "01109"
    },
    "businessType": "physical",
    "merchantCategoryCode": "5812",
    "additionalMerchantCategoryCodes": [],
    "descriptionOfServices": "Quick service restaurant",
    "grossAnnualVolume": 1200000,
    "averageTicketSize": 25.00,
    "maximumTicketSize": 2000.00,
    "contactPhone": "508-555-1234",
    "contactEmail": "ops@downtownflagship.com",
    "website": null,
    "tenderTypes": ["usdcBase", "ethereum"],
    "externalId": "store-001"
  }'
```

#### Successful response — 201

```json
{
  "id": "loc_bfdc6a7f",
  "created": "2025-06-04T16:08:03.226Z",
  "updated": "2025-06-04T16:08:03.226Z",
  "merchantId": "mer_4e5a13aa",
  "name": "Downtown Flagship",
  "isEnabled": true,
  "tenderTypes": ["usdcBase", "ethereum"],
  "address": {
    "address1": "123 Main St",
    "address2": "Suite 200",
    "city": "Springfield",
    "region": "MA",
    "country": "US",
    "postalCode": "01109"
  },
  "businessType": "physical",
  "merchantCategoryCode": "5812",
  "additionalMerchantCategoryCodes": [],
  "descriptionOfServices": "Quick service restaurant",
  "grossAnnualVolume": 1200000,
  "averageTicketSize": 25.0,
  "maximumTicketSize": 2000.0,
  "contactPhone": "508-555-1234",
  "contactEmail": "ops@downtownflagship.com",
  "website": null,
  "terminals": []
}
```

Other possible success codes: `200` (location created) and `202` (accepted for asynchronous processing).

#### Error responses

| Code | Condition                                                                   |
| ---- | --------------------------------------------------------------------------- |
| 400  | Missing or invalid fields                                                   |
| 401  | Missing or invalid API key                                                  |
| 403  | Not permitted to create locations for this merchant                         |
| 404  | Merchant `{id}` does not exist or is not visible to your key                |
| 409  | Duplicate location — a location with the same address is already registered |
| 500  | Unexpected server error                                                     |

#### Best practices

* Create under the correct merchant. Pass the correct `merchantId` so dashboards aggregate correctly.
* Use consistent naming. A clear pattern such as city plus store number makes support lookups easier.
* Disable before delete. Use `PUT /Locations/{id}/enabled` to stop new payments before decommissioning.
* Ensure address accuracy. Provide the full address up front — changing it later can affect tax or compliance data.
* Set realistic ticket sizes. `averageTicketSize` and `maximumTicketSize` should reflect real expected usage.
* Send `null` for `website` on physical locations. The field is required in the request — do not omit it.

#### Related pages

* [List Locations](/entity-management/location-management/list-locations)
* [Get Location](/entity-management/location-management/get-location)
* [Update Location](/entity-management/location-management/update-location)
* [Create Terminal](/entity-management/terminal-management/create-terminal)


# Get Location

Retrieves the full details of a single location, including address, tender type settings, and current metadata.

#### Endpoint

`GET /Locations/{id}`

#### Path parameters

| Name | Type   | Required | Description                                             |
| ---- | ------ | -------- | ------------------------------------------------------- |
| `id` | string | Yes      | The `locationId` returned when the location was created |

#### Request headers

| Header      | Value              |
| ----------- | ------------------ |
| `X-Api-Key` | `{adminApiKey}`    |
| `Accept`    | `application/json` |

#### Example request

```bash
curl -X GET "https://api.test.devs.beadpay.io/Locations/{locationId}" \
     -H "X-Api-Key: {adminApiKey}"
```

#### Successful response — 200

```json
{
  "id": "loc_bfdc6a7f",
  "created": "2025-09-02T16:08:03.226Z",
  "updated": "2025-09-02T16:08:03.226Z",
  "merchantId": "mer_4e5a13aa",
  "name": "Downtown Flagship",
  "isEnabled": true,
  "externalId": "store-001",
  "address": {
    "address1": "123 Main St",
    "address2": "Suite 200",
    "city": "Springfield",
    "region": "MA",
    "country": "US",
    "postalCode": "01109"
  },
  "businessType": "physical",
  "merchantCategoryCode": "5812",
  "additionalMerchantCategoryCodes": [],
  "descriptionOfServices": "Quick service restaurant",
  "grossAnnualVolume": 1200000,
  "highestMonthlyVolume": 150000,
  "averageTicketSize": 25.0,
  "maximumTicketSize": 2000.0,
  "contactPhone": "508-555-1234",
  "contactEmail": "ops@downtownflagship.com",
  "website": null,
  "tenderTypes": ["usdcBase", "ethereum"],
  "terminals": ["term_123", "term_456"]
}
```

#### Field reference

| Field                             | Description                                                                                              |
| --------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `id`                              | Unique `locationId`                                                                                      |
| `created`, `updated`              | ISO 8601 timestamps in UTC                                                                               |
| `merchantId`                      | Parent merchant identifier                                                                               |
| `name`                            | Friendly display name                                                                                    |
| `isEnabled`                       | Whether the location is currently accepting new transactions                                             |
| `externalId`                      | Optional external reference used by your system                                                          |
| `address.*`                       | Full postal address object                                                                               |
| `businessType`                    | `physical`, `virtual`, or `both`                                                                         |
| `merchantCategoryCode`            | Four digit MCC such as `5812`                                                                            |
| `additionalMerchantCategoryCodes` | Optional list of additional MCCs, can be empty                                                           |
| `descriptionOfServices`           | What the merchant sells or provides                                                                      |
| `grossAnnualVolume`               | Estimated annual processing volume                                                                       |
| `highestMonthlyVolume`            | Peak expected monthly volume if known                                                                    |
| `averageTicketSize`               | Typical transaction amount                                                                               |
| `maximumTicketSize`               | Maximum expected transaction amount                                                                      |
| `contactPhone`, `contactEmail`    | Primary contact details for the location                                                                 |
| `website`                         | Required when `businessType` is `virtual` or `both`, otherwise `null`                                    |
| `tenderTypes`                     | Enabled tender rails for this location                                                                   |
| `terminals`                       | Array of terminal IDs under this location. Use `GET /Locations/{id}/terminals` for full terminal details |

#### Error responses

| Code | Condition                                                    |
| ---- | ------------------------------------------------------------ |
| 401  | Missing or invalid API key                                   |
| 403  | Not permitted to view this location                          |
| 404  | Location `{id}` does not exist or is not visible to your key |

#### Best practices

* Cache the response to avoid redundant calls when rendering dashboards.
* Use the `updated` timestamp to decide when to refresh local copies.
* If the terminal list is large, use `GET /Locations/{id}/terminals` rather than reading the `terminals` array from this response.

#### Related pages

* [Update Location](/entity-management/location-management/update-location)
* [Delete Location](/entity-management/location-management/delete-location)
* [List Locations](/entity-management/location-management/list-locations)
* [List Terminals](/entity-management/terminal-management/list-terminals)


# List Locations

Returns all locations that belong to a merchant. There is no server-side pagination — the entire list is returned in one response.

#### Endpoint

`GET /Merchants/{id}/locations`

#### Path parameters

| Name | Type   | Required | Description                                       |
| ---- | ------ | -------- | ------------------------------------------------- |
| `id` | string | Yes      | The `merchantId` whose locations you want to list |

#### Request headers

| Header      | Value              |
| ----------- | ------------------ |
| `X-Api-Key` | `{adminApiKey}`    |
| `Accept`    | `application/json` |

#### Example request

```bash
curl -X GET "https://api.test.devs.beadpay.io/Merchants/{merchantId}/locations" \
     -H "X-Api-Key: {adminApiKey}"
```

#### Successful response — 200

```json
[
  {
    "id": "loc_bfdc6a7f",
    "created": "2025-09-02T16:08:03.226Z",
    "updated": "2025-09-02T16:08:03.226Z",
    "merchantId": "mer_4e5a13aa",
    "name": "Downtown Flagship",
    "isEnabled": true,
    "externalId": "store-001",
    "address": {
      "address1": "123 Main St",
      "address2": "Suite 200",
      "city": "Springfield",
      "region": "MA",
      "country": "US",
      "postalCode": "01109"
    },
    "businessType": "physical",
    "merchantCategoryCode": "5812",
    "additionalMerchantCategoryCodes": [],
    "descriptionOfServices": "Quick service restaurant",
    "grossAnnualVolume": 1200000,
    "highestMonthlyVolume": 150000,
    "averageTicketSize": 25.0,
    "maximumTicketSize": 2000.0,
    "contactPhone": "508-555-1234",
    "contactEmail": "ops@downtownflagship.com",
    "website": null,
    "tenderTypes": ["usdcBase", "ethereum"],
    "terminals": ["term_123", "term_456"]
  },
  {
    "id": "loc_c3d1e8fa",
    "created": "2025-09-02T09:15:44.112Z",
    "updated": "2025-09-02T09:15:44.112Z",
    "merchantId": "mer_4e5a13aa",
    "name": "Airport Kiosk",
    "isEnabled": true,
    "externalId": null,
    "address": {
      "address1": "987 Terminal Rd",
      "address2": null,
      "city": "Springfield",
      "region": "MA",
      "country": "US",
      "postalCode": "01109"
    },
    "businessType": "physical",
    "merchantCategoryCode": "5812",
    "additionalMerchantCategoryCodes": [],
    "descriptionOfServices": "Grab and go food",
    "grossAnnualVolume": 300000,
    "highestMonthlyVolume": 60000,
    "averageTicketSize": 18.0,
    "maximumTicketSize": 600.0,
    "contactPhone": "508-555-5678",
    "contactEmail": "airport@downtownflagship.com",
    "website": null,
    "tenderTypes": ["usdcBase"],
    "terminals": []
  }
]
```

Each array element is the same object returned by Get Location.

#### Error responses

| Code | Condition                                         |
| ---- | ------------------------------------------------- |
| 401  | Missing or invalid API key                        |
| 403  | Not permitted to view locations for this merchant |

#### Best practices

* Cache the result client-side for the session to avoid fetching the full list on every page change.
* Filter and paginate in your UI — the server returns everything, so slice locally for performance.
* Poll at most once per minute when checking for changes to stay within rate limits.
* The response does not currently support server-side pagination. Watch the changelog if cursor or page parameters are added in the future.

#### Related pages

* [Create Location](/entity-management/location-management/create-location)
* [Get Location](/entity-management/location-management/get-location)
* [List Terminals](/entity-management/terminal-management/list-terminals)


# Update Location

Replaces all editable fields of an existing location. `PUT` is a full object update — any fields you omit will be reset to their defaults, so always send the complete object you want stored.

#### Endpoint

`PUT /Locations/{id}`

#### Path parameters

| Name | Type   | Required | Description                                             |
| ---- | ------ | -------- | ------------------------------------------------------- |
| `id` | string | Yes      | The `locationId` returned when the location was created |

#### Request headers

| Header         | Value              |
| -------------- | ------------------ |
| `X-Api-Key`    | `{adminApiKey}`    |
| `Content-Type` | `application/json` |
| `Accept`       | `application/json` |

#### Request body

| Field                             | Type           | Required | Description                                                                             |
| --------------------------------- | -------------- | -------- | --------------------------------------------------------------------------------------- |
| `name`                            | string         | Yes      | Display name shown in dashboards                                                        |
| `address`                         | object         | Yes      | Provide the full address block even if only one field changed                           |
| `businessType`                    | string         | Yes      | `physical`, `virtual`, or `both`                                                        |
| `merchantCategoryCode`            | string         | Yes      | Four digit MCC such as `5812`                                                           |
| `additionalMerchantCategoryCodes` | array\<string> | Yes      | Can be an empty array                                                                   |
| `descriptionOfServices`           | string         | Yes      | What the merchant sells or provides                                                     |
| `grossAnnualVolume`               | number         | Yes      | Estimated annual processing volume                                                      |
| `averageTicketSize`               | number         | Yes      | Typical transaction amount                                                              |
| `maximumTicketSize`               | number         | Yes      | Maximum expected transaction amount                                                     |
| `contactPhone`                    | string         | Yes      | E.164 or local format accepted                                                          |
| `contactEmail`                    | string         | Yes      | Contact inbox for the location                                                          |
| `website`                         | string or null | No       | Required when `businessType` is `virtual` or `both`. Send `null` for physical locations |
| `tenderTypes`                     | array\<string> | No       | Pass an empty array `[]` to inherit merchant defaults                                   |
| `highestMonthlyVolume`            | number or null | No       | Peak expected monthly volume if known                                                   |
| `externalId`                      | string or null | No       | Optional external reference for your system                                             |

Note: `maxTransactionAmount` is not used at the location level. Use `averageTicketSize` and `maximumTicketSize` instead.

**Address object**

| Field        | Type   | Required | Description                     |
| ------------ | ------ | -------- | ------------------------------- |
| `address1`   | string | Yes      | Street line one                 |
| `address2`   | string | No       | Suite, floor, unit              |
| `city`       | string | Yes      | City or locality                |
| `region`     | string | Yes      | State or province such as `MA`  |
| `country`    | string | Yes      | ISO 3166-1 alpha-2 such as `US` |
| `postalCode` | string | Yes      | ZIP or postal code              |

#### Example request

```bash
curl -X PUT "https://api.test.devs.beadpay.io/Locations/{locationId}" \
  -H "X-Api-Key: {adminApiKey}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Downtown Flagship Renovated",
    "address": {
      "address1": "123 Main St",
      "address2": "Suite 300",
      "city": "Springfield",
      "region": "MA",
      "country": "US",
      "postalCode": "01109"
    },
    "businessType": "physical",
    "merchantCategoryCode": "5812",
    "additionalMerchantCategoryCodes": [],
    "descriptionOfServices": "Quick service restaurant",
    "grossAnnualVolume": 1200000,
    "highestMonthlyVolume": 150000,
    "averageTicketSize": 25.00,
    "maximumTicketSize": 2000.00,
    "contactPhone": "508-555-1234",
    "contactEmail": "ops@downtownflagship.com",
    "website": null,
    "tenderTypes": ["usdcBase", "ethereum"],
    "externalId": "store-001"
  }'
```

#### Successful response — 200

```json
{
  "id": "loc_bfdc6a7f",
  "created": "2025-06-04T16:08:03.226Z",
  "updated": "2025-09-02T16:22:11.015Z",
  "merchantId": "mer_4e5a13aa",
  "name": "Downtown Flagship Renovated",
  "isEnabled": true,
  "externalId": "store-001",
  "address": {
    "address1": "123 Main St",
    "address2": "Suite 300",
    "city": "Springfield",
    "region": "MA",
    "country": "US",
    "postalCode": "01109"
  },
  "businessType": "physical",
  "merchantCategoryCode": "5812",
  "additionalMerchantCategoryCodes": [],
  "descriptionOfServices": "Quick service restaurant",
  "grossAnnualVolume": 1200000,
  "highestMonthlyVolume": 150000,
  "averageTicketSize": 25.0,
  "maximumTicketSize": 2000.0,
  "contactPhone": "508-555-1234",
  "contactEmail": "ops@downtownflagship.com",
  "website": null,
  "tenderTypes": ["usdcBase", "ethereum"],
  "terminals": ["term_123", "term_456"]
}
```

#### Immutable fields

`id`, `merchantId`, and timestamp fields cannot be changed. Any attempt returns a 400 error.

#### Error responses

| Code | Condition                                                              |
| ---- | ---------------------------------------------------------------------- |
| 400  | Missing required fields, immutable field modified, or validation error |
| 401  | Missing or invalid API key                                             |
| 403  | Not permitted to update this location                                  |
| 404  | Location `{id}` does not exist or is not visible to your key           |
| 409  | Duplicate address already used by another location for this merchant   |
| 500  | Unexpected server error                                                |

#### Best practices

* Fetch the current object first, update locally, then send the full object back. This prevents accidentally nulling fields you didn't intend to change.
* Use `PUT /Locations/{id}/enabled` to disable a location before making large updates in production.
* Use the `updated` timestamp for audit trails and cache invalidation.
* Use clear, consistent location names — city and store number makes support lookups easier.

#### Related pages

* [Get Location](/entity-management/location-management/get-location)
* [List Locations](/entity-management/location-management/list-locations)
* [Delete Location](/entity-management/location-management/delete-location)


# Delete Location

Permanently removes a location and its metadata from Bead. Deletion succeeds only when the location has no terminals.

#### Endpoint

`DELETE /Locations/{id}`

#### Path parameters

| Name | Type   | Required | Description                           |
| ---- | ------ | -------- | ------------------------------------- |
| `id` | string | Yes      | The `locationId` you intend to delete |

#### Request headers

| Header      | Value              |
| ----------- | ------------------ |
| `X-Api-Key` | `{adminApiKey}`    |
| `Accept`    | `application/json` |

#### Example request

```bash
curl -X DELETE "https://api.test.devs.beadpay.io/Locations/{locationId}" \
     -H "X-Api-Key: {adminApiKey}"
```

#### Successful response

`204 No Content` — no response body is returned.

#### Before you delete

Before deleting a location, disable it with `PUT /Locations/{id}/enabled` to stop new payment activity while preserving history. Delete only once you have confirmed no active terminals remain.

If you may need the location again later, disabling is the better option — the record and its history cannot be retrieved after deletion.

#### Error responses

| Code | Condition                                                                                                             |
| ---- | --------------------------------------------------------------------------------------------------------------------- |
| 400  | Invalid request or business rule violation — common cause is attempting to delete a location that still has terminals |
| 401  | Missing or invalid API key                                                                                            |
| 403  | Not permitted to delete this location                                                                                 |
| 404  | Location `{id}` does not exist or is not visible to your key                                                          |

#### Best practices

* Disable every terminal under the location before attempting deletion.
* Use `GET /Locations/{id}` to confirm the `terminals` array is empty before calling delete.
* Archive the location details in your own system first — the record cannot be retrieved after deletion.
* Perform deletes during low-traffic windows to reduce the risk of concurrent provisioning.

#### Related pages

* [Update Location](/entity-management/location-management/update-location)
* [Get Location](/entity-management/location-management/get-location)
* [Delete Terminal](/entity-management/terminal-management/delete-terminal)


# Terminal Management

Use the Terminal Management API to provision and manage terminals that are used to create payments and receive terminal-scoped configuration such as webhooks and enablement settings.

A terminal belongs to a location and merchant and is typically the final provisioning step before payment traffic begins.

#### What you can do

* Create terminals under the correct merchant and location
* Retrieve terminal details for configuration and troubleshooting
* Update terminal settings and webhook configuration
* Disable or delete terminals that should no longer process payments
* Use terminal identifiers in Payments, Reporting, and Settlement workflows

#### Life-cycle at a glance

* **Create:** `POST /Terminals`
* **Read:** `GET /Terminals/{id}`
* **Update:** `PUT /Terminals/{id}`
* **Disable:** `PUT /Terminals/{id}` with `isEnabled=false`
* **Delete:** `DELETE /Terminals/{id}`

#### Common terminal attributes

Typical terminal records include values such as:

* `id`
* `merchantId`
* `locationId`
* `name`
* `isEnabled`
* webhook configuration fields
* created and updated timestamps

Refer to the endpoint-specific schemas and examples for the exact request and response models supported in your environment.

#### Typical workflow

1. Create or identify the parent merchant and location.
2. Create a terminal under the correct location.
3. Save the returned `terminalId`.
4. Configure webhook behavior if your integration uses event notifications.
5. Use the `terminalId` when creating payments.
6. Update, disable, or delete the terminal as your deployment changes over time.

#### Create a terminal

Create a terminal after the merchant and location are available.

```http
POST /Terminals
```

Use the returned terminal record and save the `id` for downstream payment and administration workflows.

#### Get a terminal

Retrieve a terminal when you need to inspect configuration, confirm status, or troubleshoot behavior.

```http
GET /Terminals/{id}
```

#### Update a terminal

Use `PUT`, not `PATCH`, when updating a terminal.

```http
PUT /Terminals/{id}
```

If you are updating a terminal record, send the fields required by the current endpoint contract and examples for this path.

#### Disable a terminal

To disable a terminal without deleting it, update the terminal using `PUT /Terminals/{id}` and set `isEnabled=false` in the request body where supported by the endpoint model.

```http
PUT /Terminals/{id}
```

This is useful when you want to stop new payment activity while preserving the terminal record for audit, reporting, or later reactivation.

#### Delete a terminal

Delete a terminal only when you no longer need the record for operational use.

```http
DELETE /Terminals/{id}
```

Before deleting a terminal, confirm it is not still referenced by active workflows or deployment tooling.

#### Webhooks and terminal configuration

Terminal records may include webhook-related configuration used to route payment status events to your systems.

When using terminal-level webhook configuration:

* confirm the endpoint is reachable and monitored
* validate event handling in sandbox before production rollout
* design your webhook consumer to process retries safely and idempotently

#### Operational guidance

* Keep a clear mapping between partner, merchant, location, and terminal records.
* Treat terminal changes as production-impacting configuration updates.
* Prefer disabling a terminal before deleting it when you may need to preserve operational continuity or investigate prior activity.
* Store terminal identifiers in your internal systems so payments and support workflows can reference them consistently.

#### Related APIs

Terminal Management works closely with:

* **Merchant Management** for parent merchant records
* **Location Management** for parent location records
* **Payments** for payment creation using `terminalId`
* **Reporting** and **Settlement** for downstream operational visibility

#### Troubleshooting

* **Terminal not found**\
  Confirm the `id` is correct and belongs to the expected environment.
* **Update method rejected**\
  Use `PUT /Terminals/{id}` for updates. Do not use `PATCH /Terminals/{id}`.
* **Terminal cannot be used for payments**\
  Confirm the terminal is enabled, correctly associated to the expected merchant and location, and referenced with the correct `terminalId` in the payment request.


# Terminal Lifecycle (Concepts)

A terminal moves through a simple lifecycle: creation, configuration, active use, updates, and retirement. This page explains the typical phases and the API calls commonly used at each step.

Use the top-level terminal creation pattern:

* `POST /Terminals`

Include `locationId` in the request body when creating a terminal rather than calling a nested location terminal creation URL.

### Overview

A typical terminal lifecycle looks like this:

1. Create the terminal
2. Configure or update terminal settings
3. Use the terminal in active operations
4. Manage related webhook configuration if applicable
5. Retire or delete the terminal when it is no longer needed

The allowed methods for an individual terminal resource are:

* `GET /Terminals/{id}`
* `PUT /Terminals/{id}`
* `DELETE /Terminals/{id}`

Do not use `PATCH /Terminals/{id}` unless and until the OpenAPI spec explicitly supports it.

### Phases

#### 1. Create

Create a terminal with:

* `POST /Terminals`

Provide the required terminal details in the request body, including `locationId`.

Use this phase when you are provisioning a terminal for a location and preparing it for operational use.

#### 2. Retrieve

Retrieve terminal details with:

* `GET /Terminals/{id}`

Use this to confirm the current terminal record, review configuration, or verify identifiers before making updates.

#### 3. Update

Update an existing terminal with:

* `PUT /Terminals/{id}`

Use this when you need to change terminal settings or maintain the terminal record.

If webhook configuration is managed separately in your implementation, use the webhook-specific endpoint documented for that purpose, for example:

* `PUT /Terminals/{id}/webhook`

Do not use malformed references such as:

* `PUT /Terminals/{id}PUT`
* `PATCH /Terminals/{id}`

#### 4. Active Use

Once created and configured, the terminal can be used in the flows supported by your implementation, such as payment and operational workflows tied to that terminal.

During active use, you may periodically retrieve the terminal record to confirm its current configuration:

* `GET /Terminals/{id}`

And update it as needed:

* `PUT /Terminals/{id}`

#### 5. Retirement

When a terminal is no longer needed, remove it with:

* `DELETE /Terminals/{id}`

Before deleting a terminal, confirm it is no longer required by any active workflow, integration, or reporting dependency.

### Recommended Lifecycle Pattern

A common lifecycle sequence is:

1. Create the terminal with `POST /Terminals`
2. Confirm the record with `GET /Terminals/{id}`
3. Update settings with `PUT /Terminals/{id}` as needed
4. Maintain any related webhook configuration through its documented endpoint
5. Delete the terminal with `DELETE /Terminals/{id}` when it is retired

### Endpoint Notes

Use these endpoint forms on this page:

* `POST /Terminals`
* `GET /Terminals/{id}`
* `PUT /Terminals/{id}`
* `DELETE /Terminals/{id}`

If webhook management applies, reference the webhook endpoint exactly as documented elsewhere in the terminal management docs.

Avoid these incorrect forms:

* `PATCH /Terminals/{id}`
* `PUT /Terminals/{id}PUT`

### Summary

The terminal lifecycle is centered on a straightforward create, retrieve, update, and delete pattern. Create terminals at the top level with `POST /Terminals`, include `locationId` in the request body, update terminals with `PUT /Terminals/{id}`, and retire them with `DELETE /Terminals/{id}`.


# Create Terminal

Creates a new terminal under an existing location. This endpoint is used for terminal setup and returns the terminal record along with the terminal payments API key that should be stored securely for future payment requests.

#### Endpoint

`POST /Terminals`

#### Authentication

Use your **admin API key** in the request header for terminal management operations.

> In the example below, `X-Api-Key` is used as the header name. Replace that with your standard admin API key header name if your docs use a different label.

#### Request headers

| Header         | Value              |
| -------------- | ------------------ |
| `X-Api-Key`    | `{adminApiKey}`    |
| `Content-Type` | `application/json` |
| `Accept`       | `application/json` |

#### Request body

| Field                    | Type    | Required | Description                                                                                                                             |
| ------------------------ | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                   | string  | Yes      | Friendly name for the terminal.                                                                                                         |
| `locationId`             | string  | Yes      | ID of the location that owns the terminal.                                                                                              |
| `type`                   | enum    | Yes      | Terminal type.                                                                                                                          |
| `useSandboxSharedWallet` | boolean | Yes      | Must be `true` in Sandbox for USDC and crypto tenders to function. Set at creation time only — cannot be changed via the edit endpoint. |
| `tenderTypes`            | array   | No       | Tender types enabled for this terminal.                                                                                                 |
| `description`            | string  | No       | Optional terminal description.                                                                                                          |
| `displayLogo`            | boolean | No       | Controls whether the logo is displayed where supported.                                                                                 |
| `extendRedirectUrl`      | boolean | No       | Adds `paymentPageId` as a query parameter to the redirect URL.                                                                          |
| `maxTransactionAmount`   | number  | No       | Optional maximum transaction amount for this terminal.                                                                                  |

#### Example request

```bash
curl --request POST "https://api.test.devs.beadpay.io/Terminals" \
  --header "X-Api-Key: {adminApiKey}" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{
    "name": "Front Counter 1",
    "locationId": "67f00123456789abcdef0123",
    "type": "virtual",
    "tenderTypes": ["ethereum", "usdcBase"],
    "description": "Primary checkout terminal",
    "displayLogo": true,
    "extendRedirectUrl": false,
    "maxTransactionAmount": 5000,
    "useSandboxSharedWallet": true
  }'
```

#### Example response

```json
{
  "id": "67f10123456789abcdef0456",
  "name": "Front Counter 1",
  "description": "Primary checkout terminal",
  "merchantId": "67e90123456789abcdef0001",
  "merchantStatus": "active",
  "type": "virtual",
  "merchantLocationId": "67f00123456789abcdef0123",
  "merchantLocation": {
    "id": "67f00123456789abcdef0123",
    "name": "Main Store",
    "address": {
      "address1": "123 Main St",
      "city": "Boston",
      "region": "MA",
      "country": "US",
      "postalCode": "02110"
    },
    "isEnabled": true
  },
  "displayLogo": true,
  "webhookUrl": null,
  "isEnabled": true,
  "extendRedirectUrl": false,
  "tenderTypes": ["ethereum", "usdcBase"],
  "created": "2026-04-02T15:00:00Z",
  "updated": "2026-04-02T15:00:00Z",
  "maxTransactionAmount": 5000,
  "apiKey": {
    "secret": "pk_test_terminal_secret_value",
    "maskedSecret": "pk_****_value"
  },
  "useSandboxSharedWallet": true
}
```

#### What to store

After creating a terminal, store these values securely:

* `id` — the terminal ID
* `apiKey.secret` — the terminal payments API key

Use the terminal payments API key for payment requests associated with that terminal. Do not rely on login/password credentials for new integrations.

#### Notes

* Use your **admin API key** for terminal management operations such as create, update, delete, webhook configuration, and key reset.
* Use the returned **terminal payments API key** for payment activity performed by that terminal.
* Treat `apiKey.secret` like any other secret. Do not log it or expose it in client-side code.
* `apiKey.maskedSecret` is for display purposes only and cannot be used to authenticate.

#### Error responses

<table><thead><tr><th width="108">Code</th><th>Meaning</th></tr></thead><tbody><tr><td><code>400</code></td><td>The request body is invalid or missing required fields.</td></tr><tr><td><code>401</code></td><td>The admin API key is missing or invalid.</td></tr><tr><td><code>403</code></td><td>The caller is authenticated but not allowed to create a terminal for the target location.</td></tr><tr><td><code>409</code></td><td>A conflicting terminal state or duplicate condition prevented creation.</td></tr></tbody></table>

#### Related operations

* [List terminals](/entity-management/terminal-management/list-terminals)
* [Get terminal](/entity-management/terminal-management/get-terminal)
* [Update terminal](/entity-management/terminal-management/update-terminal)
* [Delete terminal](/entity-management/terminal-management/delete-terminal)


# List Terminals

Retrieves all terminals associated with a specific merchant location.

### Endpoint

`GET https://api.test.devs.beadpay.io/Locations/{locationId}/terminals`

### Authentication

Use your **admin API key** in the request header for terminal management operations.

### Request headers

| Header      | Value              |
| ----------- | ------------------ |
| `X-Api-Key` | `{adminApiKey}`    |
| `Accept`    | `application/json` |

### Path parameter

| Name         | Type   | Description                                                  |
| ------------ | ------ | ------------------------------------------------------------ |
| `locationId` | string | Identifier of the merchant location that owns the terminals. |

### Query parameters

This endpoint does not require query parameters. It returns all terminals for the specified location.

### Example request

```bash
curl --request GET "https://api.test.devs.beadpay.io/Locations/67f00123456789abcdef0123/terminals" \
  --header "X-Api-Key: {adminApiKey}" \
  --header "Accept: application/json"
```

### Example response

```json
[
  {
    "id": "67f10123456789abcdef0456",
    "name": "Front Counter 1",
    "description": "Primary checkout terminal",
    "merchantId": "67e90123456789abcdef0001",
    "merchantStatus": "active",
    "type": "virtual",
    "merchantLocationId": "67f00123456789abcdef0123",
    "merchantLocation": {
      "id": "67f00123456789abcdef0123",
      "name": "Main Store",
      "address": {
        "address1": "123 Main St",
        "city": "Boston",
        "region": "MA",
        "country": "US",
        "postalCode": "02110"
      },
      "isEnabled": true
    },
    "displayLogo": true,
    "webhookUrl": "https://example.com/webhooks/terminal/67f10123456789abcdef0456",
    "isEnabled": true,
    "extendRedirectUrl": false,
    "tenderTypes": ["ethereum", "usdcBase"],
    "created": "2026-04-02T15:00:00Z",
    "updated": "2026-04-02T15:00:00Z",
    "maxTransactionAmount": 5000,
    "apiKey": {
      "secret": "pk_test_terminal_secret_value",
      "maskedSecret": "pk_****_value"
    },
    "useSandboxSharedWallet": true
  }
]
```

### Field descriptions

| Field                    | Type           | Description                                                           |
| ------------------------ | -------------- | --------------------------------------------------------------------- |
| `id`                     | string         | Terminal identifier.                                                  |
| `name`                   | string         | Friendly name for the terminal.                                       |
| `description`            | string         | Optional description for the terminal.                                |
| `merchantId`             | string         | Merchant that owns the terminal.                                      |
| `merchantStatus`         | string         | Current merchant status.                                              |
| `type`                   | string         | Terminal type, such as `virtual` or `physical`.                       |
| `merchantLocationId`     | string         | ID of the location that owns the terminal.                            |
| `merchantLocation`       | object         | Summary of the owning location.                                       |
| `displayLogo`            | boolean        | Indicates whether terminal branding is enabled where supported.       |
| `webhookUrl`             | string \| null | Terminal webhook URL, if configured.                                  |
| `isEnabled`              | boolean        | Indicates whether the terminal can create new payments.               |
| `extendRedirectUrl`      | boolean        | Redirect behavior setting for supported payment flows.                |
| `tenderTypes`            | array          | Tender types enabled for the terminal.                                |
| `created`                | string         | UTC ISO-8601 timestamp when the terminal was created.                 |
| `updated`                | string         | UTC ISO-8601 timestamp when the terminal was last updated.            |
| `maxTransactionAmount`   | number \| null | Optional maximum transaction amount for the terminal.                 |
| `apiKey`                 | object         | Terminal payments API key object. Treat `apiKey.secret` as sensitive. |
| `useSandboxSharedWallet` | boolean        | Sandbox wallet behavior setting for the terminal.                     |

### Notes

* Use this endpoint to review the terminals currently configured for a location.
* Use the terminal `id` from this response for follow-on terminal operations such as get, update, delete, webhook management, or cloning.
* Treat `apiKey.secret` as sensitive and do not expose it in logs or client-side code.
* Use your **admin API key** for terminal management operations. Use the terminal payments API key for payment activity performed by that terminal.

### Response codes

| Code  | Meaning                                                                                       |
| ----- | --------------------------------------------------------------------------------------------- |
| `200` | Terminals returned successfully. The array may be empty if the location has no terminals.     |
| `401` | The admin API key is missing or invalid.                                                      |
| `403` | The caller is authenticated but not permitted to access terminals for the specified location. |

### Related operations

* [Create Terminal](/entity-management/terminal-management/create-terminal)
* [Get Terminal](/entity-management/terminal-management/get-terminal)
* [Update Terminal](/entity-management/terminal-management/update-terminal)
* [Delete Terminal](/entity-management/terminal-management/delete-terminal)
* [Webhook Management](/entity-management/terminal-management/webhook-management)


# Get Terminal

Retrieves the current configuration and metadata for a specific terminal.

### Endpoint

`GET https://api.test.devs.beadpay.io/Terminals/{id}`

### Authentication

Use your **admin API key** in the request header for terminal management operations.

### Request headers

| Header      | Value              |
| ----------- | ------------------ |
| `X-Api-Key` | `{adminApiKey}`    |
| `Accept`    | `application/json` |

### Path parameter

| Name | Type   | Description                                                        |
| ---- | ------ | ------------------------------------------------------------------ |
| `id` | string | Terminal identifier returned by Create Terminal or List Terminals. |

### Request example

```bash
curl --request GET "https://api.test.devs.beadpay.io/Terminals/67f10123456789abcdef0456" \
  --header "X-Api-Key: {adminApiKey}" \
  --header "Accept: application/json"
```

### Example response

```json
{
  "id": "67f10123456789abcdef0456",
  "name": "Front Counter 1",
  "description": "Primary checkout terminal",
  "merchantId": "67e90123456789abcdef0001",
  "merchantStatus": "active",
  "type": "virtual",
  "merchantLocationId": "67f00123456789abcdef0123",
  "merchantLocation": {
    "id": "67f00123456789abcdef0123",
    "name": "Main Store",
    "address": {
      "address1": "123 Main St",
      "city": "Boston",
      "region": "MA",
      "country": "US",
      "postalCode": "02110"
    },
    "isEnabled": true
  },
  "displayLogo": true,
  "webhookUrl": "https://example.com/webhooks/terminal/67f10123456789abcdef0456",
  "isEnabled": true,
  "extendRedirectUrl": false,
  "tenderTypes": ["ethereum", "usdcBase"],
  "created": "2026-04-02T15:00:00Z",
  "updated": "2026-04-02T15:10:00Z",
  "maxTransactionAmount": 5000,
  "apiKey": {
    "secret": "pk_test_terminal_secret_value",
    "maskedSecret": "pk_****_value"
  },
  "useSandboxSharedWallet": true
}
```

### Field descriptions

| Field                    | Type           | Description                                                           |
| ------------------------ | -------------- | --------------------------------------------------------------------- |
| `id`                     | string         | Terminal identifier.                                                  |
| `name`                   | string         | Friendly name for the terminal.                                       |
| `description`            | string         | Optional description for the terminal.                                |
| `merchantId`             | string         | Merchant that owns the terminal.                                      |
| `merchantStatus`         | string         | Current status of the owning merchant.                                |
| `type`                   | string         | Terminal type, such as `virtual` or `physical`.                       |
| `merchantLocationId`     | string         | ID of the location that owns the terminal.                            |
| `merchantLocation`       | object         | Summary of the owning location.                                       |
| `displayLogo`            | boolean        | Indicates whether terminal branding is enabled where supported.       |
| `webhookUrl`             | string \| null | Terminal webhook URL, if configured.                                  |
| `isEnabled`              | boolean        | Indicates whether the terminal can create new payments.               |
| `extendRedirectUrl`      | boolean        | Redirect behavior setting for supported payment flows.                |
| `tenderTypes`            | array          | Tender types enabled for the terminal.                                |
| `created`                | string         | UTC ISO-8601 timestamp when the terminal was created.                 |
| `updated`                | string         | UTC ISO-8601 timestamp when the terminal was last updated.            |
| `maxTransactionAmount`   | number \| null | Optional maximum transaction amount for the terminal.                 |
| `apiKey`                 | object         | Terminal payments API key object. Treat `apiKey.secret` as sensitive. |
| `useSandboxSharedWallet` | boolean        | Sandbox wallet behavior setting for the terminal.                     |

### Notes

* Use this endpoint to review the current terminal configuration before making updates.
* Use the terminal `id` from this response for follow-on terminal operations such as update, delete, webhook configuration, or cloning.
* Use your **admin API key** for terminal management operations.
* Treat `apiKey.secret` as sensitive and do not expose it in logs or client-side code.

### Response codes

| Code  | Meaning                                                                         |
| ----- | ------------------------------------------------------------------------------- |
| `200` | Terminal returned successfully.                                                 |
| `401` | The admin API key is missing or invalid.                                        |
| `403` | The caller is authenticated but not permitted to access the specified terminal. |
| `404` | No terminal was found for the supplied `id`.                                    |

### Related operations

* [Create Terminal](/entity-management/terminal-management/create-terminal)
* [List Terminals](/entity-management/terminal-management/list-terminals)
* [Update Terminal](/entity-management/terminal-management/update-terminal)
* [Delete Terminal](/entity-management/terminal-management/delete-terminal)
* [Webhook Management](/entity-management/terminal-management/webhook-management)


# Reset Terminal API Key

Resets the payments API key for an existing terminal and returns the updated terminal record with the new terminal API key.

Use this endpoint when you need to rotate a terminal credential, replace a suspected exposed key, or issue a fresh key for backend payment requests.

### Endpoint

`POST https://api.test.devs.beadpay.io/Terminals/{id}/api-keys`

### Authentication

Use your **admin API key** in the request header for terminal management operations.

### Request headers

| Header      | Value              |
| ----------- | ------------------ |
| `X-Api-Key` | `{adminApiKey}`    |
| `Accept`    | `application/json` |

### Path parameter

| Name | Type   | Description                                                                       |
| ---- | ------ | --------------------------------------------------------------------------------- |
| `id` | string | Terminal identifier returned by Create Terminal, List Terminals, or Get Terminal. |

### Request body

This endpoint does not require a request body.

### Example request

```bash
curl --request POST "https://api.test.devs.beadpay.io/Terminals/67f10123456789abcdef0456/api-keys" \
  --header "X-Api-Key: {adminApiKey}" \
  --header "Accept: application/json"
```

### Example response

```json
{
  "id": "67f10123456789abcdef0456",
  "name": "Front Counter 1",
  "description": "Primary checkout terminal",
  "merchantId": "67e90123456789abcdef0001",
  "merchantStatus": "active",
  "type": "virtual",
  "merchantLocationId": "67f00123456789abcdef0123",
  "merchantLocation": {
    "id": "67f00123456789abcdef0123",
    "name": "Main Store",
    "address": {
      "address1": "123 Main St",
      "city": "Boston",
      "region": "MA",
      "country": "US",
      "postalCode": "02110"
    },
    "isEnabled": true
  },
  "displayLogo": true,
  "webhookUrl": "https://example.com/webhooks/terminal/67f10123456789abcdef0456",
  "isEnabled": true,
  "extendRedirectUrl": false,
  "tenderTypes": ["ethereum", "usdcBase"],
  "created": "2026-04-02T15:00:00Z",
  "updated": "2026-04-03T11:20:00Z",
  "maxTransactionAmount": 5000,
  "apiKey": {
    "secret": "pk_test_new_terminal_secret_value",
    "maskedSecret": "pk_****_value"
  },
  "useSandboxSharedWallet": true
}
```

### What to do after reset

After resetting the key:

1. Store the new `apiKey.secret` securely.
2. Update any backend service, secret manager, or environment configuration that uses the prior terminal payments API key.
3. Use the new terminal payments API key in the `X-Api-Key` header for future Payments API requests.
4. Do not use `apiKey.maskedSecret` for authentication.

Payments authentication is header-based and uses the full terminal API key. The masked value is display-only.

### Field notes

| Field                 | Description                                                                   |
| --------------------- | ----------------------------------------------------------------------------- |
| `id`                  | Terminal identifier.                                                          |
| `apiKey.secret`       | New terminal payments API key. Store securely and use for Payments API calls. |
| `apiKey.maskedSecret` | Masked display value only. Not valid for authentication.                      |
| `updated`             | UTC ISO-8601 timestamp reflecting the terminal update after key rotation.     |

### Notes

* Use your **admin API key** for this terminal-management operation.
* Use the returned **terminal payments API key** for payment activity performed by that terminal.
* Treat `apiKey.secret` like a password and avoid logging or exposing it in client-side code.
* This endpoint is intended for credential rotation and operational maintenance.

### Response codes

| Code  | Meaning                                                                      |
| ----- | ---------------------------------------------------------------------------- |
| `200` | Terminal API key reset successfully.                                         |
| `201` | Terminal API key reset successfully and returned as a created response.      |
| `202` | Reset accepted for processing.                                               |
| `400` | The request was invalid.                                                     |
| `401` | The admin API key is missing or invalid.                                     |
| `403` | The caller is authenticated but not permitted to reset the terminal API key. |
| `409` | A conflicting terminal state prevented key reset.                            |

### Related operations

* [Create Terminal](/entity-management/terminal-management/create-terminal)
* [List Terminals](/entity-management/terminal-management/list-terminals)
* [Get Terminal](/entity-management/terminal-management/get-terminal)
* [Update Terminal](/entity-management/terminal-management/update-terminal)
* [Delete Terminal](/entity-management/terminal-management/delete-terminal)
* [Webhook Management](/entity-management/terminal-management/webhook-management)


# Update Terminal

Updates an existing terminal configuration.

#### Endpoint

`PUT https://api.test.devs.beadpay.io/Terminals/{id}`

#### Authentication

Use your **admin API key** in the request header for terminal management operations.

#### Request headers

| Header         | Value              |
| -------------- | ------------------ |
| `X-Api-Key`    | `{adminApiKey}`    |
| `Content-Type` | `application/json` |
| `Accept`       | `application/json` |

#### Path parameter

| Name | Type   | Description                                                                       |
| ---- | ------ | --------------------------------------------------------------------------------- |
| `id` | string | Terminal identifier returned by Create Terminal, List Terminals, or Get Terminal. |

#### Request body

| Field                  | Type    | Required | Description                                                    |
| ---------------------- | ------- | -------- | -------------------------------------------------------------- |
| `name`                 | string  | Yes      | Friendly name for the terminal.                                |
| `description`          | string  | Yes      | Terminal description.                                          |
| `displayLogo`          | boolean | Yes      | Controls whether the logo is displayed where supported.        |
| `isEnabled`            | boolean | Yes      | Controls whether the terminal can create new payments.         |
| `tenderTypes`          | array   | No       | Tender types enabled for this terminal.                        |
| `extendRedirectUrl`    | boolean | No       | Adds `paymentPageId` as a query parameter to the redirect URL. |
| `maxTransactionAmount` | number  | No       | Optional maximum transaction amount for this terminal.         |

> **`useSandboxSharedWallet` cannot be set or changed via this endpoint.** This Sandbox-only setting is applied at terminal creation time only. If a terminal is missing `useSandboxSharedWallet: true` and USDC payments are failing as a result, the terminal must be recreated with the flag included. See [Create Terminal](/entity-management/terminal-management/create-terminal).

#### Example request

```bash
curl --request PUT "https://api.test.devs.beadpay.io/Terminals/67f10123456789abcdef0456" \
  --header "X-Api-Key: {adminApiKey}" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{
    "name": "Front Counter 1",
    "description": "Primary checkout terminal - updated",
    "displayLogo": true,
    "isEnabled": true,
    "tenderTypes": ["ethereum", "usdcBase"],
    "extendRedirectUrl": false,
    "maxTransactionAmount": 7500
  }'
```

#### Example response

```json
{
  "id": "67f10123456789abcdef0456",
  "name": "Front Counter 1",
  "description": "Primary checkout terminal - updated",
  "merchantId": "67e90123456789abcdef0001",
  "merchantStatus": "active",
  "type": "virtual",
  "merchantLocationId": "67f00123456789abcdef0123",
  "merchantLocation": {
    "id": "67f00123456789abcdef0123",
    "name": "Main Store",
    "address": {
      "address1": "123 Main St",
      "city": "Boston",
      "region": "MA",
      "country": "US",
      "postalCode": "02110"
    },
    "isEnabled": true
  },
  "displayLogo": true,
  "webhookUrl": "https://example.com/webhooks/terminal/67f10123456789abcdef0456",
  "isEnabled": true,
  "extendRedirectUrl": false,
  "tenderTypes": ["ethereum", "usdcBase"],
  "created": "2026-04-02T15:00:00Z",
  "updated": "2026-04-03T10:15:00Z",
  "maxTransactionAmount": 7500,
  "apiKey": {
    "secret": "pk_test_terminal_secret_value",
    "maskedSecret": "pk_****_value"
  },
  "useSandboxSharedWallet": true
}
```

#### Notes

* Use this endpoint to update terminal settings such as name, description, enabled status, tender types, branding, and transaction limits.
* `useSandboxSharedWallet` is not editable via this endpoint. It is a creation-time setting. If it is missing from an existing terminal, recreate the terminal with `"useSandboxSharedWallet": true` using `POST /Terminals`. Terminals created automatically from a boarding approval have this set correctly by default.
* Use your **admin API key** for terminal management operations.
* Use the terminal payments API key for payment activity performed by that terminal.
* Treat `apiKey.secret` as sensitive and do not expose it in logs or client-side code.
* Updating a terminal does not replace the terminal payments API key. If you need to rotate the terminal key, use the terminal API key reset operation.

#### Response codes

| Code  | Meaning                                                                         |
| ----- | ------------------------------------------------------------------------------- |
| `200` | Terminal updated successfully.                                                  |
| `202` | Update accepted for processing.                                                 |
| `204` | Update completed with no response body.                                         |
| `400` | The request body is invalid or missing required fields.                         |
| `401` | The admin API key is missing or invalid.                                        |
| `403` | The caller is authenticated but not permitted to update the specified terminal. |
| `404` | No terminal was found for the supplied `id`.                                    |
| `409` | A conflicting terminal state prevented the update.                              |

#### Related operations

* [Create Terminal](/entity-management/terminal-management/create-terminal)
* [List Terminals](/entity-management/terminal-management/list-terminals)
* [Get Terminal](/entity-management/terminal-management/get-terminal)
* [Delete Terminal](/entity-management/terminal-management/delete-terminal)
* [Webhook Management](/entity-management/terminal-management/webhook-management)


# Delete Terminal

Deletes a terminal that is no longer needed and has no associated payment history.

Use this endpoint for cleanup of unused terminals. If a terminal has associated payments, it should not be deleted. Terminals with payment history are retained for auditability, reconciliation, reporting, and historical traceability.

### Endpoint

`DELETE https://api.test.devs.beadpay.io/Terminals/{id}`

### Authentication

Use your **admin API key** in the request header for terminal management operations.

### Request headers

| Header      | Value              |
| ----------- | ------------------ |
| `X-Api-Key` | `{adminApiKey}`    |
| `Accept`    | `application/json` |

### Path parameter

| Name | Type   | Description                                                                       |
| ---- | ------ | --------------------------------------------------------------------------------- |
| `id` | string | Terminal identifier returned by Create Terminal, List Terminals, or Get Terminal. |

### Request body

This endpoint does not require a request body.

### Example request

```bash
curl --request DELETE "https://api.test.devs.beadpay.io/Terminals/67f10123456789abcdef0456" \
  --header "X-Api-Key: {adminApiKey}" \
  --header "Accept: application/json"
```

### Success response

A successful delete returns:

`204 No Content`

No response body is returned on success.

### Before you delete

Use delete only when the terminal has not been used for payment activity and no longer needs to be retained.

If a terminal has associated payments, it should not be deleted. Instead:

* keep the terminal for audit and historical reference
* disable it so it cannot be used for new payment activity
* retain the terminal ID relationship for existing payment records

In practice, disabling the terminal is the better option when you want to stop future use but preserve historical payment integrity.

### Error responses

| Code  | Meaning                                                                                                                                         |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `204` | Terminal deleted successfully.                                                                                                                  |
| `400` | The request is invalid. A common example is attempting to delete a terminal that has associated payments and must be retained for auditability. |
| `401` | The admin API key is missing or invalid.                                                                                                        |
| `403` | The caller is authenticated but not permitted to delete the specified terminal.                                                                 |
| `404` | No terminal was found for the supplied `id`.                                                                                                    |

### Notes

* Deletion is intended for unused terminals.
* If you need to stop a terminal from being used without removing its history, update the terminal and set it to disabled instead.
* Historical payments should continue to reference the original terminal that created them.
* Deleting a terminal should not be used as a substitute for hiding or deactivating a terminal that already has transaction history.

### Related operations

* [List Terminals](/entity-management/terminal-management/list-terminals)
* [Get Terminal](/entity-management/terminal-management/get-terminal)
* [Update Terminal](/entity-management/terminal-management/update-terminal)
* [Reset Terminal API Key](/entity-management/terminal-management/reset-terminal-api-key)
* [Webhook Management](/entity-management/terminal-management/webhook-management)


# Webhook Management

Configure or remove the default webhook URL for a terminal. Each terminal can have one default webhook URL for payment status events. You can also provide payment-specific webhook URLs when creating a payment.

**Set or update the webhook URL**

Use this operation to create or replace the default payment status webhook URL for a terminal.

**Endpoint**

`PUT https://api.test.devs.beadpay.io/Terminals/{id}/webhook`

**Authentication**

Use your **admin API key** in the request header for terminal management operations. This endpoint does not accept a terminal payments API key. Using a terminal payments API key will return `403 Forbidden`.

**Request headers**

| Header         | Value              |
| -------------- | ------------------ |
| `X-Api-Key`    | `{adminApiKey}`    |
| `Content-Type` | `application/json` |
| `Accept`       | `application/json` |

**Path parameter**

| Name | Type   | Description                                                                       |
| ---- | ------ | --------------------------------------------------------------------------------- |
| `id` | string | Terminal identifier returned by Create Terminal, List Terminals, or Get Terminal. |

**Request body**

| Field | Type         | Required | Description                                                                                                       |
| ----- | ------------ | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `url` | string (URI) | Yes      | Fully qualified HTTPS URL where terminal-level payment status events should be delivered. Maximum 512 characters. |

**Example request**

```bash
curl --request PUT "https://api.test.devs.beadpay.io/Terminals/67f10123456789abcdef0456/webhook" \
  --header "X-Api-Key: {adminApiKey}" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{
    "url": "https://yourapp.com/webhooks/payment-status"
  }'
```

**Example response**

```json
{
  "url": "https://yourapp.com/webhooks/payment-status",
  "signingSecret": "{base64EncodedSecret}"
}
```

Store `signingSecret` securely. Use it to verify the `x-webhook-signature` header on all incoming terminal webhook deliveries. Treat it like a password and do not log it or expose it in client-side code.

**Success responses**

| Code  | Meaning                                  |
| ----- | ---------------------------------------- |
| `200` | Webhook URL set or updated successfully. |
| `202` | Request accepted for processing.         |
| `204` | Request completed with no response body. |

**Error responses**

| Code  | Meaning                                                                                                                                              |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | The request body is invalid, such as a missing or malformed URL.                                                                                     |
| `401` | The admin API key is missing or invalid.                                                                                                             |
| `403` | The caller is authenticated but not permitted to manage the specified terminal, or a terminal payments API key was used instead of an admin API key. |
| `404` | No terminal was found for the supplied `id`.                                                                                                         |
| `409` | A conflicting terminal state prevented the update.                                                                                                   |

**Delete the webhook URL**

Use this operation to remove the default webhook URL from a terminal. After removal, terminal-level payment status events are no longer delivered to a default webhook URL.

**Endpoint**

`DELETE https://api.test.devs.beadpay.io/Terminals/{id}/webhook`

**Authentication**

Use your **admin API key** in the request header for terminal management operations.

**Request headers**

| Header      | Value              |
| ----------- | ------------------ |
| `X-Api-Key` | `{adminApiKey}`    |
| `Accept`    | `application/json` |

**Path parameter**

| Name | Type   | Description                                                                       |
| ---- | ------ | --------------------------------------------------------------------------------- |
| `id` | string | Terminal identifier returned by Create Terminal, List Terminals, or Get Terminal. |

**Example request**

```bash
curl --request DELETE "https://api.test.devs.beadpay.io/Terminals/67f10123456789abcdef0456/webhook" \
  --header "X-Api-Key: {adminApiKey}" \
  --header "Accept: application/json"
```

**Success response**

`204 No Content` — no response body is returned on success.

**Error responses**

| Code  | Meaning                                                                         |
| ----- | ------------------------------------------------------------------------------- |
| `400` | The request is invalid.                                                         |
| `401` | The admin API key is missing or invalid.                                        |
| `403` | The caller is authenticated but not permitted to manage the specified terminal. |
| `404` | No terminal was found for the supplied `id`.                                    |

**Signature verification**

When you set or update a terminal webhook, the response includes a `signingSecret`. Store it securely — you will use it to verify the `x-webhook-signature` header on every incoming terminal webhook delivery.

The signature header format is:

```
x-webhook-signature: t=1781811428956,s=FK/SW9lIK0CXpNnfweTN3ZbJ8Nvbm1RF69Nm6XE8w3O=
```

Where `t` is a Unix epoch timestamp in **milliseconds** and `s` is a **Base64-encoded** HMAC-SHA256 digest of the signed message `t + "." + rawBody`, computed using the base64-decoded bytes of `signingSecret` as the HMAC key.

For full verification steps and a Node.js code example, see [How do I verify that a webhook really came from Bead?](/faqs-and-troubleshooting/webhooks-and-error-codes/how-do-i-verify-that-a-webhook-really-came-from-bead)

**Delivery behavior**

The terminal webhook is the default event destination for all payments on that terminal. You can also supply payment-specific `webhookUrls` when creating a payment if a single payment needs additional event destinations.

Note that `x-webhook-signature` is only included on terminal-level webhook deliveries. It is not present on per-payment deliveries sent to `webhookUrls`.

**Best practices**

* Return a `2xx` response quickly from your webhook consumer.
* Make webhook processing idempotent so retries do not create duplicate work.
* Use environment-specific webhook URLs for Sandbox and Production.
* Treat the signing secret like any other credential and do not log it or expose it in client-side code.

**Notes**

* The request body field is `url`, not `webhookUrl`.
* Use your **admin API key** for terminal webhook management.
* Use the terminal payments API key for payment operations only, not for terminal configuration changes.

**Related operations**

* [Create Terminal](/entity-management/terminal-management/create-terminal)
* [Get Terminal](/entity-management/terminal-management/get-terminal)
* [Update Terminal](/entity-management/terminal-management/update-terminal)
* [Delete Terminal](/entity-management/terminal-management/delete-terminal)
* [Reset Terminal API Key](/entity-management/terminal-management/reset-terminal-api-key)


# Managing Tender Types

Bead supports multiple tender types, including crypto rails, digital wallets, and alternative payment methods. A terminal’s `tenderTypes` show which approved payment methods are available for use on that terminal.

Tender enablement is established upstream through onboarding, pricing, approval, and provider enrollment. Entity Management then reflects that approved tender set through the merchant, location, and terminal records.

Use this page to understand how tender types flow into terminal configuration, how to verify what is currently active on a terminal, and when a tender change requires onboarding or operational support rather than a terminal-only update.

### Tender enablement vs. terminal configuration

Tender enablement and terminal configuration are related, but they are not the same thing.

Tender enablement is the process of approving a merchant to accept a payment method. This is driven by the merchant application, commercial terms, pricing, provider requirements, and approval status.

Terminal configuration controls how the already-approved tender set is reflected or narrowed for a specific terminal, channel, or payment experience.

A terminal should not be treated as the primary place to enroll a merchant into a new tender type. If a tender type was not part of the merchant’s approved application, pricing, and provider setup, adding it at the terminal level may trigger additional enrollment flows, provider requirements, or operational review.

In production, those changes are normally coordinated through the ISO, boarding integrator, or Bead support team.

### How tender types are determined

Tender availability flows through several layers.

| Layer                   | Purpose                                                                                                 |
| ----------------------- | ------------------------------------------------------------------------------------------------------- |
| Onboarding and approval | Captures the merchant’s requested services, pricing, supporting information, and approval requirements. |
| Partner configuration   | Defines the tender types commercially available for a partner program.                                  |
| Merchant configuration  | Represents the tender types the merchant is approved to accept.                                         |
| Location configuration  | Reflects the tender types available for a merchant location.                                            |
| Terminal configuration  | Reflects or narrows the approved tender set for a specific terminal or payment experience.              |

Most payment integrators interact with terminal and payment APIs, but the tender types available there depend on the merchant’s upstream approval and configuration.

### How tender types get set

Tender type enablement is primarily an onboarding function.

A typical flow works like this:

1. A merchant application is submitted with the services, fees, and pricing information for the tender types the merchant is intended to accept.
2. The application is reviewed and approved.
3. Required provider enrollment or activation is completed for the applicable tender types.
4. Entity Management creates or updates the merchant, location, and terminal records with the approved tender set.
5. The terminal reflects the tender types that are active for that merchant and location.

This means the terminal record is an important source of truth for what is currently available, but it is not the source of approval for new tenders.

### Verifying tender types on a terminal

To see which tender types are currently active on a terminal, retrieve the terminal:

`GET /Terminals/{terminalId}`

The response includes a `tenderTypes` array showing the tender types available for that terminal.

Use this endpoint when validating a payment integration, troubleshooting why a tender is not appearing, or confirming which payment methods can be offered for a specific terminal.

If a tender type is missing from the terminal response, do not assume it can be added as a simple terminal edit. First confirm whether the merchant, location, and provider setup are approved for that tender type.

### Partner-level configuration

At the partner level, Bead enables the tender types that are commercially available for the partner program.

This configuration is based on commercial agreements, supported payment rails, provider availability, rates, fees, and other onboarding requirements.

Partners do not normally configure tender types directly through terminal APIs. The partner configuration defines the universe of tender types that can become available to merchants and terminals downstream.

### Merchant-level configuration

At the merchant level, tender types reflect what the merchant is approved to accept.

When a merchant completes onboarding and approval, the merchant record is configured with the appropriate tender types based on the application, pricing, and provider setup.

Merchant-level tender types are not typically changed ad hoc. Adding a new tender type may require updated pricing, provider enrollment, additional review, or a merchant update process.

### Location-level configuration

Locations can reflect the tender types available for a specific merchant location.

In most cases, a location follows the merchant’s approved tender configuration. Where supported, location configuration can help represent differences between business locations, sales channels, or operational setups.

A location cannot make an unavailable tender type available if the merchant has not been approved and configured for that tender type.

### Terminal-level configuration

A terminal represents a specific payment endpoint or payment experience.

Terminal `tenderTypes` can be used to reflect or narrow the approved tender set for that terminal. For example, a merchant may have multiple terminals for different use cases, such as in-store checkout, eCommerce checkout, mobile checkout, or a specific hosted payment flow.

Terminal configuration can be used to:

* Verify which tender types are active on a terminal.
* Limit a terminal to a subset of approved tender types.
* Create different payment experiences across terminals.
* Exclude tender types that are not relevant for a specific channel or use case.

Terminal configuration should not be used to enroll a merchant into a tender type that was not approved through onboarding, pricing, and provider setup.

### Example use cases

#### Support a narrower in-store payment experience

A merchant may be approved for several tender types, but an in-store terminal may only need to support a subset of those methods. The terminal can be configured to reflect only the tender types relevant to that in-store flow.

#### Support a different eCommerce payment experience

A merchant may use a separate terminal for online checkout. That terminal can reflect tender types appropriate for the eCommerce flow, as long as those tender types are already approved for the merchant and location.

#### Verify a terminal before creating payments

Before creating payments against a terminal, retrieve the terminal and review the `tenderTypes` array. This confirms which tender types are active for that terminal.

#### Add a newly approved tender later

If a merchant is later approved for an additional tender type, Bead or the boarding integrator may need to complete the required approval, provider, and configuration steps before the tender can be used on terminals.

### API interaction

Tender types appear in terminal creation, terminal updates, and terminal retrieval.

Relevant endpoints include:

* `GET /Terminals/{terminalId}`
* `POST /Terminals`
* `PUT /Terminals/{terminalId}`

Use `GET /Terminals/{terminalId}` to verify the tender types currently active on a terminal.

When creating or updating a terminal, any provided `tenderTypes` should be understood as operating within the merchant and location’s approved tender set. If `tenderTypes` are omitted during terminal creation, Bead applies the default or inherited tender configuration for the location.

For full request and response examples, refer to the Terminal Management documentation.

### Relationship to payment requests

Payment requests may also include tender type selection depending on the payment flow.

Passing `tenderTypes` on a payment request does not enable a new tender type for the merchant or terminal. It only narrows or directs the payment experience to tender types that are already available for the merchant, location, and terminal.

If `tenderTypes` are omitted from the payment request, Bead can use the available tender configuration for the terminal and hosted payment experience.

### Common questions

#### How do I verify which tender types are active on a terminal?

Use `GET /Terminals/{terminalId}`. The response includes a `tenderTypes` array showing the tender types currently available on that terminal.

#### Do I need to set tender types during merchant onboarding?

Tender type approval is handled through the merchant onboarding, pricing, and provider setup process. Integrators should make sure the merchant application and commercial setup reflect the tender types the merchant is expected to accept.

#### Can merchants have different tender types across terminals?

Yes. Terminals can reflect different subsets of the merchant’s approved tender types. For example, one terminal may support a narrower in-store payment experience while another supports an online checkout flow.

#### Can I enable a new tender type by editing a terminal?

Not by terminal configuration alone. If the merchant is not already approved and configured for that tender type, additional onboarding, pricing, provider enrollment, or operational support may be required.

#### What happens if a tender type is missing from the terminal response?

Treat the tender type as unavailable for that terminal. Confirm whether the merchant and location are approved for that tender type before attempting to update the terminal.

#### What happens if new tender types are added later?

After the required approval and configuration steps are complete, newly approved tender types can be reflected in merchant, location, and terminal configuration.

### Recommended integration pattern

1. Complete merchant onboarding and tender approval.
2. Confirm the merchant, location, and terminal records have been created.
3. Use `GET /Terminals/{terminalId}` to verify active tender types.
4. Create or update terminals only within the approved tender set.
5. Use payment-level `tenderTypes` only to narrow or direct the payment experience.
6. If a tender type is missing, coordinate through the ISO, boarding integrator, or Bead support team before treating it as a terminal-only configuration change.


# Settlement

The Settlement section helps integrators understand how completed payments are grouped, calculated, funded, and reconciled.

Use Settlement when you need to connect completed payment activity to settlement records, batch groupings, payment-level settlement detail, and deposit or funding information.

Settlement works together with Payments and [Reporting](/reporting):

* Payments describe individual transactions.
* [Reporting](/reporting) APIs return payment history by partner, merchant, terminal, tender type, status, date range, and reference.
* Settlement APIs explain how completed payments were grouped, settled, and funded.

If you need payment search or transaction history, start with [Reporting](/reporting). If you need settlement grouping, fee breakdowns, payout timing, funding status, or reconciliation details, use Settlement.

Building residual or revenue-share reports? Start with [Settlements](/settlement/settlements). It is the authoritative source for transaction fees, deposit fees, adjustments, and net fundable amount per settlement period. Then see the [Residual Reporting](/settlement/residual-reporting) guide for the full integration pattern.

#### When to use Settlement

Use Settlement when you need to:

* Review gross amount, fees, adjustments, and net payout for a settlement period.
* Show merchant settlement history.
* Build daily or monthly reconciliation jobs.
* Build partner residual reporting.
* Connect settled funds back to individual payments.
* Review batch-level settlement activity.
* Check payment-level settlement status.
* Confirm deposit or funding information.
* Investigate settlement-related support questions.
* Match Bead settlement activity to your internal ledger or accounting system.

#### How it works

Settlement starts after a payment has completed and becomes eligible for settlement.

A typical reconciliation flow is:

1. Use [Reporting](/reporting) APIs to identify the payments you care about.
2. Use `GET /Merchants/{merchantId}/settlements` to retrieve settlement records for a merchant.
3. Use [Settlement](/settlement/settlements) records to review period-level fee totals, adjustments, and net fundable amount.
4. Use `GET /Settlements/{id}/batches` to review batch-level groupings and connect settlements back to payments.
5. Use payment settlement fields on each PaymentResponse to review payment-level settlement status.
6. Use [Batches](/settlement/batches) and [Deposits](/settlement/deposits) when you need to reconcile funded amounts back to payments.

#### Core settlement concepts

**Settlements**

A settlement is the top-level funding record for a settlement period. It rolls up all batch activity for a merchant into a single record that captures gross fundable amount, total transaction fees, deposit fees, adjustments, and net fundable amount.

Use [Settlements](/settlement/settlements) when you need fee totals, net payout amounts, adjustment detail, or the basis for residual and revenue-share reporting. Retrieve settlement records for a specific merchant using `GET /Merchants/{merchantId}/settlements`.

**Payments**

A payment is the original transaction created through the Payments API. Payment records include transaction-level information such as amount, tender type, status, merchant, terminal, reference, and tracking identifiers.

Every PaymentResponse includes settlement fields (`settlementId`, `batchId`, `batchPeriod`, `batchStatus`) that show where the payment sits in the settlement lifecycle. These fields populate as the payment moves through batching and settlement.

**Payment settlement detail**

Payment settlement detail explains how individual payments connect to settlement activity, including which batch they belong to, which settlement covers them, and their current settlement status.

Use payment settlement detail when you need to understand how an individual payment settled, which batch it belongs to, the settlement status, and when it was included in a settlement. See [Payment Settlement Detail](/settlement/payment-settlement-detail).

**Batches**

Batches group payments for settlement processing and reconciliation.

Use [Batches](/settlement/batches) when you need to understand which payments were grouped together, review batch-level fee totals, and how they relate to a settlement record.

**Deposits**

Deposits represent funding activity associated with settlement.

Use [Deposits](/settlement/deposits) when you need to confirm funded amounts, deposit status, deposit method, deposit fees, or the settlement record associated with a deposit.

#### Relationship to Reporting

[Reporting](/reporting) focuses on payment history. Use Reporting APIs when you need lists of payments and transaction-level fields.

Settlement focuses on how completed payments were grouped, calculated, and funded. Use Settlement APIs when you need settlement fee breakdowns, batch relationships, payment-level settlement context, deposit information, and settlement status.

A common pattern is to use [Reporting](/reporting) APIs to find the payments for a report or investigation, then use Settlement APIs to understand how those payments were settled and funded.

#### Files, exports, and API reporting

Bead exposes settlement, batch, deposit, and payment-level settlement data through APIs.

Most integrations should use the documented APIs for reconciliation. If a partner needs a custom settlement export or file delivery process, that should be handled as a separate implementation requirement with Bead.

#### Typical integration flows

**Merchant settlement history**

Goal: show settlement history for a merchant.

1. Identify the merchant in Bead.
2. Retrieve settlement records using `GET /Merchants/{merchantId}/settlements`.
3. Display settlement amount, status, settlement date, and net fundable amount.
4. Allow the user to select a settlement for batch and payment-level detail.

**Payment-level settlement review**

Goal: explain how a specific payment settled.

1. Start with a payment from [Reporting](/reporting) APIs or a known payment identifier.
2. Retrieve the payment using `GET /Merchants/{merchantId}/payments` and review `settlementId`, `batchId`, `batchPeriod`, and `batchStatus` on the PaymentResponse.
3. If `settlementId` is set, retrieve the settlement using `GET /Settlements/{id}` to review fee totals and net fundable amount.
4. Use `GET /Settlements/{id}/batches` for batch context and to connect the payment to the broader settlement.

**Deposit reconciliation**

Goal: reconcile funded deposits back to settlement activity.

1. Review the deposit record.
2. Use the related settlement information to identify the settlement activity behind the deposit.
3. Review batch and payment-level settlement information as needed.
4. Match deposit amount, settlement status, fees, and related payment activity to your internal ledger.

**Residual and revenue-share reporting**

Goal: calculate the revenue Bead retained on behalf of a partner for a settlement period for use in partner residual reporting.

1. Retrieve settlement records for the merchant and date range.
2. Review `grossFundableAmount`, `totalTransactionFees`, `depositFees`, `adjustments`, and `netFundableAmount` on each settlement.
3. Compute retained revenue per merchant per period.
4. Apply your internal payout schedule to produce residual amounts.

See [Residual Reporting](/settlement/residual-reporting) for the full step-by-step pattern and field-level guidance.

**Support investigation**

Goal: answer a merchant or partner question about settlement timing or funding.

1. Use [Reporting](/reporting) APIs to locate the original payment or group of payments.
2. Review `settlementId`, `batchId`, and `batchStatus` on the PaymentResponse to check settlement status.
3. Use `GET /Settlements/{id}/batches` to understand the batch-level settlement event.
4. Review [Deposits](/settlement/deposits) if the question is about funded movement to the destination account.

#### Next steps

* Use [Settlements](/settlement/settlements) for period-level fee totals, adjustments, and net fundable amount.
* Use [Residual Reporting](/settlement/residual-reporting) to build partner residual reports.
* Use [Payment Settlement Detail](/settlement/payment-settlement-detail) for payment-level settlement context and to trace payments through the settlement lifecycle.
* Use [Batches](/settlement/batches) when you need grouped payment batch records.
* Use [Deposits](/settlement/deposits) when you need funded movement and deposit status.
* Use [Reporting](/reporting) when you need payment history before reviewing settlement.
* Review Settlement status codes in the [Reference Guide](/reference-guide) when interpreting settlement states.
* Review Settlement currencies in the [Reference Guide](/reference-guide) when interpreting settlement currency and tender information.


# Settlements

Settlements represent the periodic funding events that group completed payment batches into a single net payout for a merchant.

Use Settlements when you need to review the fee breakdown behind a funding event, reconcile gross and net amounts, investigate adjustments, or build residual or revenue-share reports for a settlement period.

If you are building partner residual reporting, start here. The Settlement record is the authoritative source for total transaction fees, deposit fees, adjustments, and net fundable amount for a settlement period. See [Residual Reporting](/settlement/residual-reporting) for the full integration pattern.

#### When to use Settlements

Use Settlements when you need to:

* Review the gross amount, total transaction fees, deposit fees, adjustments, and net fundable amount for a settlement period.
* Confirm the settlement date for a merchant.
* Connect a settlement to the batches and deposits associated with it.
* Build monthly or periodic residual or revenue-share reports.
* Investigate settlement-level fee or adjustment activity.
* Match a Bead settlement to your internal ledger or accounting system.

For payment history and transaction-level search, use [Reporting](/reporting) APIs first. For payment-level settlement detail, use [Payment Settlement Detail](/settlement/payment-settlement-detail). For funded bank movement, use [Deposits](/settlement/deposits).

#### How it works

A settlement is created when Bead closes a period of batch activity for a merchant and prepares a net payout.

The settlement record rolls up:

* The gross fundable amount from all batches in the period.
* Total transaction fees withheld across those batches.
* A deposit fee for the ACH or wire transfer used to fund the merchant.
* Any adjustments (chargebacks, refund recoveries, platform fees, holdbacks, and others).
* The resulting net fundable amount, which is the amount dispatched to the merchant.

A typical settlement review flow is:

1. Retrieve settlement records for a merchant and date range.
2. Review gross fundable amount, total transaction fees, deposit fees, adjustments, and net fundable amount.
3. Use the batch IDs to connect the settlement to grouped payment activity.
4. Use the deposit records to confirm funded movement and funding method.
5. Use the settlement record as the basis for any residual or revenue-share calculation.

#### Available endpoints

| Scope                     | Path                                    | Description                                                                                     |
| ------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Settlement list           | GET /Settlements                        | Returns settlement records with filters for date range, status, type, merchant, and pagination. |
| Merchant settlement list  | GET /Merchants/{merchantId}/settlements | Returns settlement records for a specific merchant.                                             |
| Single settlement         | GET /Settlements/{id}                   | Returns one settlement record by ID.                                                            |
| Batches for a settlement  | GET /Settlements/{id}/batches           | Returns batch records associated with a settlement.                                             |
| Deposits for a settlement | GET /Settlements/{id}/deposits          | Returns deposit records associated with a settlement.                                           |

#### Query parameters

**GET /Settlements and GET /Merchants/{merchantId}/settlements**

| Parameter     | Type             | Description                                                           |
| ------------- | ---------------- | --------------------------------------------------------------------- |
| From          | date             | Start of the date range filter.                                       |
| To            | date             | End of the date range filter.                                         |
| Status        | string           | Filter by settlement state. See Settlement state.                     |
| Type          | string           | Filter by currency category: crypto, walletApps, or fiat.             |
| MerchantIds   | array of strings | Filter by one or more merchant IDs. Applies to GET /Settlements only. |
| Page          | integer          | Page number. Defaults to 0.                                           |
| PageSize      | integer          | Results per page. Defaults to 50, maximum 100.                        |
| SortBy        | string           | Field to sort by.                                                     |
| SortDirection | string           | Sort direction: asc or desc.                                          |

> **Date format:** `From` and `To` accept date-only values (`2026-06-22`). ISO date-times with a `.000Z` suffix fail validation.

#### Settlement fields

A settlement record includes:

| Field                | Type                | Description                                                                            |
| -------------------- | ------------------- | -------------------------------------------------------------------------------------- |
| id                   | string              | Unique settlement identifier.                                                          |
| merchantId           | string              | Merchant associated with the settlement.                                               |
| type                 | string              | Settlement currency category.                                                          |
| networkTypes         | array of strings    | Network types included in the settlement.                                              |
| status               | string              | Current settlement state. See Settlement state.                                        |
| batchIds             | array of strings    | Batch IDs included in this settlement.                                                 |
| payoutId             | string or null      | Payout reference associated with this settlement, when applicable.                     |
| grossFundableAmount  | number or string    | Total payment volume before fees and adjustments.                                      |
| totalTransactionFees | number or string    | Sum of all per-transaction fees withheld across the batches in this settlement period. |
| depositFees          | number or string    | Fee for the ACH or wire transfer used to fund this settlement.                         |
| adjustments          | array of Adjustment | List of adjustments applied to this settlement. See Adjustments.                       |
| netFundableAmount    | number or string    | Net amount funded to the merchant after fees and adjustments.                          |
| outgoingAchIds       | array of strings    | Outgoing ACH identifiers associated with the settlement.                               |
| settlementDate       | date-time or null   | Actual settlement date, when completed.                                                |
| created              | date-time           | Date and time the settlement record was created.                                       |
| updated              | date-time           | Date and time the settlement record was last updated.                                  |

#### Net fundable amount formula

The `netFundableAmount` represents what Bead funds to the merchant after all deductions:

`netFundableAmount = grossFundableAmount − totalTransactionFees − depositFees ± adjustments`

For residual reporting, the revenue Bead retained from a merchant for a settlement period is:

`retained = grossFundableAmount − netFundableAmount`

See [Residual Reporting](/settlement/residual-reporting) for the full partner integration pattern.

#### Settlement state

Settlement state indicates where the settlement is in the funding lifecycle.

| Value      | Description                                                           |
| ---------- | --------------------------------------------------------------------- |
| cleared    | Settlement has cleared and is ready for funding.                      |
| hold       | Settlement is on hold and pending review before funding proceeds.     |
| dispatched | Settlement funds have been dispatched to the merchant's bank account. |
| paid       | Settlement funds have been confirmed as paid.                         |

#### Adjustments

Adjustments modify the net fundable amount and represent non-transaction items applied to a settlement.

An adjustment object includes:

| Field         | Type             | Description                                                        |
| ------------- | ---------------- | ------------------------------------------------------------------ |
| type          | string           | Adjustment category. See adjustment types below.                   |
| note          | string or null   | Optional note describing the adjustment.                           |
| netAdjustment | number or string | The adjustment amount. Negative values reduce net fundable amount. |

Supported adjustment types:

| Type                  | Description                                                   |
| --------------------- | ------------------------------------------------------------- |
| chargeback            | Chargeback amount recovered from settlement.                  |
| chargebackFee         | Fee assessed for a chargeback.                                |
| refund                | Refund amount recovered from settlement.                      |
| achReject             | ACH reject recovery applied to settlement.                    |
| holdback              | Reserve holdback applied to settlement.                       |
| reversal              | Reversal amount applied to settlement.                        |
| complianceFine        | Compliance fine assessed against settlement.                  |
| platformFee           | Platform fee assessed against settlement.                     |
| equipmentFee          | Equipment fee assessed against settlement.                    |
| adjustmentCredit      | Credit applied to settlement (increases net fundable amount). |
| currencyConversionFee | Fee for currency conversion applied to settlement.            |
| networkAssessmentFee  | Network assessment fee applied to settlement.                 |
| depositCorrection     | Correction to a prior deposit applied to settlement.          |
| taxWithholding        | Tax withholding amount applied to settlement.                 |

When reviewing adjustments for residual reporting, negative adjustments reduce the amount Bead retains and should be accounted for in revenue-share calculations.

#### Relationship to other Settlement pages

Settlements are the top-level funding record in the Settlement model.

Use [Batches](/settlement/batches) to understand how individual payments were grouped within a settlement period.

Use [Deposits](/settlement/deposits) to confirm funded bank movement and deposit status for a settlement.

Use [Payment Settlement Detail](/settlement/payment-settlement-detail) for payment-level settlement context and to trace payments through the settlement lifecycle.

A common pattern is:

1. Use [Reporting](/reporting) APIs to identify payments in scope.
2. Use Settlements to review fee totals, adjustments, and net fundable amount for the period.
3. Use [Batches](/settlement/batches) to reconcile grouped payment activity.
4. Use [Deposits](/settlement/deposits) to confirm funded movement.

#### Typical integration flows

**Review settlement fee breakdown for a merchant**

Goal: understand gross amount, fees, adjustments, and net payout for a settlement period.

1. Retrieve settlement records for the merchant and date range using `GET /Merchants/{merchantId}/settlements`.
2. Review `grossFundableAmount`, `totalTransactionFees`, `depositFees`, `adjustments`, and `netFundableAmount`.
3. Apply the net fundable amount formula to verify the net payout.
4. Review any adjustments for type and amount.

**Connect a settlement to batches and payments**

Goal: reconcile a settlement back to the grouped payments behind it.

1. Retrieve the settlement record.
2. Use `batchIds` to retrieve the associated batches via `GET /Settlements/{id}/batches`.
3. Use batch `paymentIds` to retrieve the underlying payments.
4. Verify that batch-level fee totals roll up to the settlement's `totalTransactionFees`.

**Reconcile a settlement to a deposit**

Goal: confirm that a settlement was funded and match the funded amount.

1. Retrieve the settlement record.
2. Use `GET /Settlements/{id}/deposits` to retrieve associated deposits.
3. Review deposit amount, `feeAmount`, status, and `dispatchedDate`.
4. Confirm that the deposit `feeAmount` matches the settlement `depositFees`.

**Build a monthly residual report**

Goal: calculate revenue retained by Bead per merchant for a settlement period for partner residual reporting.

See [Residual Reporting](/settlement/residual-reporting) for the full step-by-step pattern and field-level guidance.

#### Next steps

* Use [Residual Reporting](/settlement/residual-reporting) to understand how to assemble settlement data for partner residual reporting.
* Use [Batches](/settlement/batches) to review grouped payment activity.
* Use [Deposits](/settlement/deposits) to confirm funded bank movement.
* Use [Payment Settlement Detail](/settlement/payment-settlement-detail) for payment-level settlement context and to trace payments through the settlement lifecycle.
* Use [Reporting](/reporting) APIs when you need payment history before reviewing settlement.


# Residual Reporting

Residual reporting gives partners visibility into the fee revenue Bead collected across their merchant portfolio during a settlement period.

Bead operates on a net daily settlement model: fees are withheld from each merchant's gross payment volume before funds are settled into the merchant's account. Those fees accumulate through the settlement period and are reconciled at month end.

This page explains where revenue and fee data lives in the Bead API and how to assemble it for a monthly residual report.

**How Bead retains fees**

When a payment settles:

1. Bead groups completed payments into daily batches.
2. Each batch carries a `grossFundableAmount` (total payment volume) and a `totalFees` amount (transaction-based fees withheld).
3. At the end of the settlement period, Bead closes the batches into a Settlement record.
4. The Settlement record captures `totalTransactionFees` (sum of all batch fees for the period), `depositFees` (the ACH or wire fee for funding), and any `adjustments` (chargebacks, refunds, platform fees, etc.).
5. Bead funds the merchant the `netFundableAmount` — gross minus all fees and adjustments.
6. The retained fees are reconciled at month end and made available to the partner for reporting and payout purposes.

**Fee and revenue fields by entity**

| Field                           | Entity               | API field              | Notes                                                        |
| ------------------------------- | -------------------- | ---------------------- | ------------------------------------------------------------ |
| Gross payment volume            | SettlementResponse   | `grossFundableAmount`  | Total payment volume before any deductions                   |
| Transaction fees (period total) | SettlementResponse   | `totalTransactionFees` | Sum of all per-transaction fees across batches in the period |
| Transaction fees (per batch)    | PaymentBatchResponse | `totalFees`            | Per-batch subtotal; rolls up to `totalTransactionFees`       |
| Deposit / funding fee           | SettlementResponse   | `depositFees`          | ACH or wire fee for the funding transfer                     |
| Deposit / funding fee           | DepositResponse      | `feeAmount`            | Same fee, visible at the deposit record level                |
| Adjustments                     | SettlementResponse   | `adjustments[]`        | Chargebacks, refunds, platform fees, holdbacks, and others   |
| Net payout to merchant          | SettlementResponse   | `netFundableAmount`    | What the merchant received after all deductions              |

**Transaction fees are not tracked at the individual payment level.** They are aggregated at the batch level (`totalFees`) and at the settlement level (`totalTransactionFees`). If you need per-payment fee detail, contact Bead — this is not currently available in the standard API responses.

**Recommended API pattern for monthly residual reporting**

**Step 1: Identify the settlement period**

Determine the date range for the reporting month. Use `From` and `To` date filters on the `/Settlements` endpoint.

**Step 2: Retrieve settlements for the period**

```
GET /Settlements?MerchantIds[]=<merchantId>&From=<startDate>&To=<endDate>
```

Retrieve one page at a time. Use `Page` and `PageSize` (up to 100 per page). Repeat until you have all records for the period.

For partners with many merchants, pass an array of `MerchantIds` to filter to the merchants in your portfolio.

**Step 3: For each settlement, capture the revenue and fee fields**

From each `SettlementResponse`, collect:

* `merchantId`
* `id` (settlement ID)
* `grossFundableAmount`
* `totalTransactionFees`
* `depositFees`
* `adjustments` — iterate the array and note type and amount for each
* `netFundableAmount`
* `targetPayoutDate` or `settlementDate`
* `status` — filter to `dispatched` or `paid` to include only funded settlements

**Step 4: Optionally validate against batch-level fees**

To cross-check settlement-level totals against batch-level detail:

```
GET /Settlements/{id}/batches
```

Sum `totalFees` across all batches for the settlement. This should equal `totalTransactionFees` on the settlement record. If there is a discrepancy, contact Bead support.

**Step 5: Optionally validate deposit fees**

```
GET /Settlements/{id}/deposits
```

The `feeAmount` on the deposit record should match `depositFees` on the settlement. Use this when auditing funding fees.

**Adjustments**

Adjustments represent non-transaction items that modify the net settlement amount. They are included in the `adjustments` array on the `SettlementResponse` and should be reviewed when assembling a complete revenue picture for the period.

| Adjustment type         | Description                                     |
| ----------------------- | ----------------------------------------------- |
| `chargeback`            | Chargeback amount recovered from the merchant   |
| `chargebackFee`         | Fee assessed for a chargeback                   |
| `refund`                | Refund amount recovered from the merchant       |
| `achReject`             | ACH reject recovery                             |
| `holdback`              | Reserve holdback                                |
| `reversal`              | Reversal amount                                 |
| `complianceFine`        | Compliance fine assessed against the settlement |
| `platformFee`           | Platform fee assessed against the settlement    |
| `equipmentFee`          | Equipment fee assessed against the settlement   |
| `adjustmentCredit`      | Credit applied to the settlement                |
| `currencyConversionFee` | Currency conversion fee                         |
| `networkAssessmentFee`  | Network assessment fee                          |
| `depositCorrection`     | Correction to a prior deposit                   |
| `taxWithholding`        | Tax withholding amount                          |

Review the `note` field on each adjustment when present — it provides additional context for the specific item.

**Example: fee data for a single merchant over one month**

Assume Bead closed three settlements for a merchant during the month.

| Settlement | Gross   | Transaction Fees | Deposit Fee | Adjustments        | Net to Merchant |
| ---------- | ------- | ---------------- | ----------- | ------------------ | --------------- |
| S-001      | $20,000 | $480             | $0.25       | -$150 (chargeback) | $19,669.75      |
| S-002      | $18,000 | $432             | $0.25       | none               | $17,567.75      |
| S-003      | $12,000 | $288             | $0.25       | +$50 (credit)      | $11,761.75      |

For the period, the data available to the partner is:

* Total gross volume: $50,000
* Total transaction fees: $1,200
* Total deposit fees: $0.75
* Net adjustments: -$100 (chargeback net of credit)
* Total net to merchant: $49,000

This is the revenue and fee data Bead surfaces per merchant per settlement period. How partners apply this data to their internal payout schedules is handled outside the Bead API.

**Pagination and data completeness**

The `/Settlements` endpoint returns up to 100 records per page. For partners with large merchant portfolios or long reporting windows, paginate fully before computing totals.

Check that all settlements in the period have a `status` of `dispatched` or `paid` before including them in a final report. Settlements in `hold` or `cleared` state have not yet been funded and may change before month end.

**Multi-merchant portfolios**

Use the `MerchantIds` array filter to limit results to the merchants in your portfolio. Retrieve and aggregate per merchant to build a complete revenue picture across your book for the period.

**Next steps**

* Use [Settlements](/settlement) to retrieve settlement records and fee fields.
* Use [Batches](/settlement/batches) to validate batch-level fee totals.
* Use [Deposits](/settlement/deposits) to validate deposit fees.
* Use [Merchant Settlements](broken://pages/zKSBYYX80HC72lJTvqVi) for the merchant-level settlement event view.
* Use [Reporting](/reporting) APIs when you need the underlying payment activity for a period.
* Contact Bead if you need per-payment fee detail or a custom settlement export.


# Batches

Batches group completed payments for settlement processing and reconciliation.

Use Batches when you need to see which payments were grouped together, review batch-level fee totals, or connect grouped payment activity to a settlement record.

For payment history and transaction-level search, use [Reporting](/reporting) first. For the settlement-level fee rollup, use [Settlements](/settlement/settlements). For payment-level settlement detail, use [Payment Settlement Detail](/settlement/payment-settlement-detail). For funded bank movement, use [Deposits](/settlement/deposits).

#### When to use Batches

Use Batches when you need to:

* Review which payments were grouped into a batch.
* See batch-level gross amount, fees, and net fundable amount.
* Validate that batch fee totals roll up to the settlement's `totalTransactionFees`.
* Filter batch activity by merchant, settlement, date range, status, or type.
* Connect batch activity to a settlement record.
* Reconcile a settlement back to the individual payments behind it.

#### How it works

When payments complete, Bead groups them into a batch for the settlement period. Each batch carries a `grossFundableAmount`, a `totalFees` amount representing the transaction fees withheld, and a `netFundableAmount`. When the settlement period closes, batches are rolled into a [Settlement](/settlement/settlements) record. The sum of `totalFees` across all batches equals the `totalTransactionFees` on the Settlement.

A typical batch reconciliation flow:

1. Start with a merchant, settlement ID, or date range.
2. Retrieve batch records using the relevant filters.
3. Review gross fundable amount, total fees, net fundable amount, and transaction count.
4. Use `paymentIds` to connect the batch back to individual payments.
5. Use `settlementId` to connect the batch to the parent settlement record.

#### Available endpoints

| Scope              | Path                                | Description                                                                                                     |
| ------------------ | ----------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Batch list         | GET /Batches                        | Returns batch records with filters for date range, status, type, merchant, settlement, pagination, and sorting. |
| Single batch       | GET /Batches/{id}                   | Returns one batch record by batch ID.                                                                           |
| Merchant batches   | GET /Batches/merchants/{merchantId} | Returns batch records for a specific merchant.                                                                  |
| Settlement batches | GET /Settlements/{id}/batches       | Returns batch records associated with a settlement.                                                             |

#### Query parameters

| Parameter     | Type             | Description                                               |
| ------------- | ---------------- | --------------------------------------------------------- |
| From          | date             | Start of the date range filter.                           |
| To            | date             | End of the date range filter.                             |
| Status        | string           | Filter by batch status. See Batch status.                 |
| Type          | string           | Filter by currency category: crypto, walletApps, or fiat. |
| MerchantIds   | array of strings | Filter by one or more merchant IDs.                       |
| SettlementIds | array of strings | Filter by one or more settlement IDs.                     |
| Page          | integer          | Page number. Defaults to 0.                               |
| PageSize      | integer          | Results per page. Defaults to 50, maximum 100.            |
| SortBy        | string           | Field to sort by.                                         |
| SortDirection | string           | Sort direction: asc or desc.                              |

#### Batch fields

| Field               | Type              | Description                                                                                        |
| ------------------- | ----------------- | -------------------------------------------------------------------------------------------------- |
| id                  | string            | Unique batch identifier.                                                                           |
| merchantId          | string            | Merchant associated with the batch.                                                                |
| status              | string            | Current batch status. See Batch status.                                                            |
| type                | string            | Batch currency category. See Batch type.                                                           |
| totalFees           | number or string  | Transaction fees withheld for this batch. Sums to `totalTransactionFees` on the parent Settlement. |
| grossFundableAmount | number or string  | Total payment volume in the batch before fees.                                                     |
| netFundableAmount   | number or string  | Net amount after fees.                                                                             |
| transactionCount    | integer or string | Number of payments included in the batch.                                                          |
| paymentIds          | array of strings  | Payment IDs included in the batch.                                                                 |
| batchPeriod         | date              | Date associated with the batch period.                                                             |
| settlementId        | string or null    | Settlement associated with the batch, when assigned.                                               |

#### Batch status

| Value  | Description                                         |
| ------ | --------------------------------------------------- |
| open   | Batch is open and accepting payments.               |
| closed | Batch has been closed and submitted for settlement. |
| hold   | Batch is on hold pending review.                    |

#### Batch type

| Value      | Description                         |
| ---------- | ----------------------------------- |
| crypto     | Batch contains crypto payments.     |
| walletApps | Batch contains wallet app payments. |
| fiat       | Batch contains fiat card payments.  |

#### Fee rollup

Batch fees roll up to the settlement level. For any settlement, the sum of `totalFees` across all associated batches equals the `totalTransactionFees` on the Settlement record.

Use this to cross-check settlement-level fee totals against batch-level detail. If there is a discrepancy, contact Bead support.

#### Typical integration flows

**Review batches for a merchant**

Goal: show batch activity for a merchant.

1. Retrieve batches filtered by `MerchantIds` and date range.
2. Display batch status, type, batch period, gross fundable amount, total fees, net fundable amount, and transaction count.
3. Allow the user to select a batch for payment-level detail.

**Reconcile a settlement to batches**

Goal: understand which batches are included in a settlement.

1. Use `GET /Settlements/{id}/batches` to retrieve batches for the settlement.
2. Review each batch's gross fundable amount, `totalFees`, net fundable amount, and transaction count.
3. Confirm that the sum of `totalFees` across all batches matches `totalTransactionFees` on the settlement.

**Reconcile a batch to payments**

Goal: connect a batch back to the payments in it.

1. Retrieve the batch record.
2. Use `paymentIds` to retrieve the underlying payments. See [Payment Settlement Detail](/settlement/payment-settlement-detail) to review settlement context for those payments.
3. Match batch totals to your internal ledger.

#### Next steps

* Use [Settlements](/settlement/settlements) for the period-level fee rollup, adjustments, and net fundable amount.
* Use [Residual Reporting](/settlement/residual-reporting) to understand how batch fees feed into monthly residual calculations.
* Use [Deposits](/settlement/deposits) to confirm funded bank movement tied to a settlement.
* Use [Payment Settlement Detail](/settlement/payment-settlement-detail) for payment-level settlement context and to trace payments through the settlement lifecycle.
* Use [Reporting](/reporting) when you need payment history before reviewing batches.


# Deposits

Deposits represent the funded bank movement associated with a settlement.

Use Deposits when you need to confirm whether a settlement has been funded, review the deposited amount and fee, match a deposit to a settlement record, or answer merchant questions about when funds arrived.

For payment history and transaction-level search, use [Reporting](/reporting) first. For the settlement-level fee and net amount, use [Settlements](/settlement/settlements). For payment-level settlement detail, use [Payment Settlement Detail](/settlement/payment-settlement-detail).

#### When to use Deposits

Use Deposits when you need to:

* Confirm whether settlement funds have been dispatched or paid.
* Review the funded amount and deposit fee for a settlement.
* Validate that `feeAmount` on the deposit matches `depositFees` on the parent Settlement.
* Match a deposit to a settlement record.
* Filter deposit activity by merchant, settlement, date range, status, or method.
* Investigate a merchant or partner question about funded settlement activity.
* Reconcile deposited funds to your internal ledger or accounting system.

#### How it works

When Bead funds a settlement, it creates a Deposit record representing the bank transfer to the merchant's account. The deposit carries the funded amount, the `feeAmount` for the ACH or wire transfer, and a reference back to the parent `settlementId`.

The `feeAmount` on the deposit record is the same fee captured as `depositFees` on the Settlement. Use this to cross-validate the two records during reconciliation.

A typical deposit reconciliation flow:

1. Start with a merchant, settlement ID, or date range.
2. Retrieve deposit records using the relevant filters.
3. Review deposit status, amount, fee amount, method, and dispatched date.
4. Use `settlementId` to connect the deposit back to the parent settlement record.
5. Compare `feeAmount` to `depositFees` on the settlement to confirm they match.

#### Available endpoints

| Scope               | Path                           | Description                                                                                                         |
| ------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| Deposit list        | GET /Deposits                  | Returns deposit records with filters for date range, status, method, merchant, settlement, pagination, and sorting. |
| Single deposit      | GET /Deposits/{id}             | Returns one deposit record by deposit ID.                                                                           |
| Settlement deposits | GET /Settlements/{id}/deposits | Returns deposit records associated with a settlement.                                                               |

#### Query parameters

| Parameter     | Type             | Description                                    |
| ------------- | ---------------- | ---------------------------------------------- |
| From          | date             | Start of the date range filter.                |
| To            | date             | End of the date range filter.                  |
| Status        | string           | Filter by deposit status. See Deposit status.  |
| Method        | string           | Filter by deposit method: ach or wire.         |
| MerchantIds   | array of strings | Filter by one or more merchant IDs.            |
| SettlementIds | array of strings | Filter by one or more settlement IDs.          |
| Page          | integer          | Page number. Defaults to 0.                    |
| PageSize      | integer          | Results per page. Defaults to 50, maximum 100. |
| SortBy        | string           | Field to sort by.                              |
| SortDirection | string           | Sort direction: asc or desc.                   |

#### Deposit fields

| Field                 | Type             | Description                                                                       |
| --------------------- | ---------------- | --------------------------------------------------------------------------------- |
| id                    | string           | Unique deposit identifier.                                                        |
| merchantId            | string           | Merchant associated with the deposit.                                             |
| destinationMerchantId | string           | Destination merchant for the funded movement.                                     |
| settlementId          | string           | Settlement associated with this deposit.                                          |
| status                | string           | Current deposit status. See Deposit status.                                       |
| amount                | number or string | Funded amount transferred to the merchant.                                        |
| feeAmount             | number or string | Fee for the ACH or wire transfer. Matches `depositFees` on the parent Settlement. |
| method                | string           | Funding rail used. See Deposit method.                                            |
| maskedRoutingNumber   | string           | Masked routing number for reconciliation and support reference.                   |
| maskedAccountNumber   | string           | Masked account number for reconciliation and support reference.                   |
| created               | date-time        | Date and time the deposit record was created.                                     |
| updated               | date-time        | Date and time the deposit record was last updated.                                |
| dispatchedDate        | date-time        | Date and time the deposit was dispatched.                                         |
| transferType          | string           | Type of funding movement. See Transfer type.                                      |

Masked bank fields are provided for reconciliation and support reference. Full bank account details are not returned in deposit responses.

#### Deposit status

| Value      | Description                                       |
| ---------- | ------------------------------------------------- |
| dispatched | Deposit has been sent to the merchant's bank.     |
| paid       | Deposit has been confirmed as received.           |
| error      | Deposit encountered an error and requires review. |

#### Deposit method

| Value | Description                          |
| ----- | ------------------------------------ |
| ach   | Funds transferred via ACH.           |
| wire  | Funds transferred via wire transfer. |

#### Transfer type

| Value      | Description                                         |
| ---------- | --------------------------------------------------- |
| sale       | Standard settlement funding for completed payments. |
| refund     | Funding movement associated with a refund.          |
| chargeback | Funding movement associated with a chargeback.      |

#### Fee validation

The `feeAmount` on a deposit record should always match the `depositFees` field on the parent Settlement. Use this check when auditing deposit fees during reconciliation.

If there is a discrepancy, contact Bead support.

#### Typical integration flows

**Reconcile deposits for a merchant**

Goal: show deposit activity for a merchant.

1. Retrieve deposits filtered by `MerchantIds` and date range.
2. Display deposit amount, fee amount, status, method, settlement ID, and dispatched date.
3. Link each deposit to the related settlement for further detail.

**Confirm a settlement was funded**

Goal: verify that a settlement has been paid out.

1. Use `GET /Settlements/{id}/deposits` to retrieve deposits for the settlement.
2. Review deposit status, amount, `feeAmount`, method, and `dispatchedDate`.
3. Confirm `feeAmount` matches `depositFees` on the settlement record.

**Investigate a funding question**

Goal: answer a merchant or partner question about a deposit.

1. Search deposits by merchant, settlement ID, or date range.
2. Review deposit status and dispatched date.
3. Confirm the deposit method, transfer type, and masked bank reference.
4. Use `settlementId` to pull the related settlement and batch detail if needed.

#### Next steps

* Use [Settlements](/settlement/settlements) for the period-level fee rollup, adjustments, and net fundable amount.
* Use [Residual Reporting](/settlement/residual-reporting) for guidance on how deposit fees factor into monthly residual calculations.
* Use [Batches](/settlement/batches) to review grouped payment activity behind a settlement.
* Use [Payment Settlement Detail](/settlement/payment-settlement-detail) for payment-level settlement context and to trace payments through the settlement lifecycle.
* Use [Reporting](/reporting) when you need payment history before reviewing deposits.


# Payment Settlement Detail

Payment settlement detail explains how individual payments connect to settlement activity, including which batch they belong to, which settlement covers them, and their current settlement status.

Use this when you need to trace a specific payment through the settlement lifecycle, build payment-level reconciliation reports, or investigate whether and how a payment has settled.

For period-level fee totals and net fundable amounts, use Settlements. For batch-level groupings and fee rollups, use Batches. For funded bank movement, use Deposits.

#### When to use Payment Settlement Detail

Use payment settlement detail when you need to:

* Connect a completed payment to its settlement and batch.
* Check whether a payment has been batched.
* Review batch period and batch status for a payment.
* Build reconciliation reports that combine payment and settlement data.
* Investigate a payment's settlement status.
* Identify the payments behind a specific settlement.

#### How it works

Every PaymentResponse includes four settlement fields that show where a payment sits in the settlement lifecycle: `settlementId`, `batchId`, `batchPeriod`, and `batchStatus`. These fields populate as the payment moves through the batch and settlement workflow. Payments that have not yet been batched return null for all four fields.

To navigate from a settlement down to its payments, use the batch path: retrieve batches for the settlement using `GET /Settlements/{id}/batches`, then use the `paymentIds` array on each batch to identify the underlying payments.

To navigate in the other direction, starting from a payment, fetch the payment from `GET /Merchants/{merchantId}/payments` and read the settlement fields directly from the response.

A typical payment settlement review flow:

1. Retrieve settlement records for a merchant using `GET /Merchants/{merchantId}/settlements`.
2. Select the settlement to review.
3. Retrieve batches for that settlement using `GET /Settlements/{id}/batches`.
4. Use `paymentIds` on each batch to identify the payments included.
5. Review `settlementId`, `batchId`, `batchPeriod`, and `batchStatus` on each payment to confirm settlement context.

#### Settlement fields on PaymentResponse

Every payment returned from `GET /Merchants/{merchantId}/payments` includes the following settlement fields:

| Field        | Type           | Description                                                                           |
| ------------ | -------------- | ------------------------------------------------------------------------------------- |
| settlementId | string or null | Settlement covering this payment. Null until the payment is included in a settlement. |
| batchId      | string or null | Batch this payment belongs to. Null until the payment is batched.                     |
| batchPeriod  | date or null   | Date associated with the batch period.                                                |
| batchStatus  | string or null | Current status of the batch. See Batch status.                                        |

A payment progresses through this state sequence:

* `batchId` null, `settlementId` null: payment is completed but not yet batched.
* `batchId` set, `settlementId` null: payment is in a batch not yet tied to a settlement.
* `batchId` set, `settlementId` set: payment is part of a closed settlement.

#### Available endpoints

| Scope                    | Path                                    | Description                                                               |
| ------------------------ | --------------------------------------- | ------------------------------------------------------------------------- |
| Merchant settlement list | GET /Merchants/{merchantId}/settlements | Returns settlement records for a specific merchant.                       |
| Batches for a settlement | GET /Settlements/{id}/batches           | Returns batch records for a settlement. Each batch includes paymentIds.   |
| Merchant payment list    | GET /Merchants/{merchantId}/payments    | Returns payments for a merchant. Each payment includes settlement fields. |

#### Query parameters

**GET /Merchants/{merchantId}/settlements**

| Parameter     | Type    | Description                                               |
| ------------- | ------- | --------------------------------------------------------- |
| From          | date    | Start of the date range filter.                           |
| To            | date    | End of the date range filter.                             |
| Status        | string  | Filter by settlement state. See Settlement state.         |
| Type          | string  | Filter by currency category: crypto, walletApps, or fiat. |
| Page          | integer | Page number. Defaults to 0.                               |
| PageSize      | integer | Results per page. Defaults to 50, maximum 100.            |
| SortBy        | string  | Field to sort by.                                         |
| SortDirection | string  | Sort direction: asc or desc.                              |

> **Date format:** `From` and `To` accept date-only values (`2026-06-22`). ISO date-times with a `.000Z` suffix fail validation.

**GET /Merchants/{merchantId}/payments**

| Parameter     | Type             | Description                                    |
| ------------- | ---------------- | ---------------------------------------------- |
| From          | date-time        | Start of the date range filter.                |
| To            | date-time        | End of the date range filter.                  |
| StatusCode    | array of strings | Filter by payment status.                      |
| TenderType    | array of strings | Filter by tender type.                         |
| Page          | integer          | Page number. Defaults to 0.                    |
| PageSize      | integer          | Results per page. Defaults to 50, maximum 100. |
| SortBy        | string           | Field to sort by.                              |
| SortDirection | string           | Sort direction: asc or desc.                   |

> **Filtering by settlement:** `GET /Merchants/{merchantId}/payments` does not support filtering by `settlementId` or `batchId`. To retrieve the payments belonging to a specific settlement, use `GET /Settlements/{id}/batches` and read `paymentIds` from each batch record.

#### Typical integration flows

**Trace a settlement to its payments**

Goal: identify the payments that make up a settlement.

1. Retrieve the settlement using `GET /Settlements/{id}` or `GET /Merchants/{merchantId}/settlements`.
2. Retrieve batches for the settlement using `GET /Settlements/{id}/batches`.
3. Review each batch for `paymentIds`, `batchPeriod`, `status`, and fee totals.
4. Use the `paymentIds` list to retrieve or cross-reference individual payments.

**Check settlement status for a payment**

Goal: determine whether a specific payment has been settled.

1. Retrieve the payment using `GET /Merchants/{merchantId}/payments`.
2. Check `batchId` on the PaymentResponse.
3. If `batchId` is null, the payment has not yet been batched. Confirm `statusCode` is `completed` as only completed payments are eligible for settlement.
4. If `batchId` is set but `settlementId` is null, the payment is in a batch not yet tied to a settlement.
5. If `settlementId` is set, retrieve the settlement using `GET /Settlements/{id}` to review status, fees, and net fundable amount.

**Build a payment-level reconciliation report**

Goal: combine payment and settlement data for a reporting window.

1. Retrieve settlements for the merchant and date range using `GET /Merchants/{merchantId}/settlements`.
2. For each settlement, retrieve batches using `GET /Settlements/{id}/batches`.
3. Use `paymentIds` from each batch to retrieve the underlying payments.
4. Group by `settlementId` and `batchId` for reconciliation context.
5. Match payment amounts to batch fee totals and the settlement's `netFundableAmount`.

**Investigate an unsettled payment**

Goal: determine why a completed payment has not appeared in a settlement.

1. Retrieve the payment and review `batchId`.
2. If `batchId` is null, the payment has not been batched. Confirm `statusCode` is `completed` as only completed payments are eligible.
3. If `batchId` is set, retrieve the batch using `GET /Settlements/{id}/batches` and check batch `status`.
4. A batch with status `hold` is pending review. Contact Bead support if the hold is unexpected.

#### Next steps

* Use [Settlements](/settlement/settlements) for period-level fee totals, adjustments, and net fundable amount.
* Use [Batches](/settlement/batches) to review grouped payment activity and verify fee rollups.
* Use [Deposits](/settlement/deposits) to confirm funded bank movement tied to a settlement.
* Use [Reporting](/reporting) when you need payment history before reviewing settlement.


# Settlement Reconciliation Patterns

## Settlement Reconciliation Patterns

This page explains how to reconcile settlement activity across a full merchant portfolio efficiently, as a scheduled job rather than a loop that runs once per merchant. It also covers the timestamp, status field, and pagination details that most often trip up reconciliation jobs.

Use [Settlement data and deposit reconciliation](/faqs-and-troubleshooting/settlement-faqs/settlement-data-and-deposit-reconciliation) if you need the field level reference for tracing one deposit back to one settlement. Use this page if you're building a recurring job that reconciles many merchants at once.

#### When to use this page

Use these patterns when you need to:

* Run a nightly or scheduled reconciliation job across many merchants under a partner.
* Detect settlement status changes incrementally, without pulling everything again each cycle.
* Decide which status or amount field is authoritative when more than one looks relevant.
* Understand why a payment record doesn't reflect a settlement event you know happened.
* Avoid unnecessary per merchant API calls in a reconciliation loop.

#### The pattern: pull once for the portfolio, not once per merchant

[GET /Settlements](/settlement/settlements) accepts a `MerchantIds` array. If you serve multiple merchants under a partner, pass all of their merchant IDs in a single call rather than looping per merchant:

```
GET /Settlements?From=2026-07-15&To=2026-07-16&MerchantIds=mer_1&MerchantIds=mer_2&MerchantIds=mer_3
```

This returns settlement records for every merchant in the list in one paginated response. The same bulk filtering approach applies to [Batches](/settlement/batches), which accepts both `MerchantIds` and `SettlementIds` arrays. Building your reconciliation job around list calls with array filters, rather than one call per merchant per entity, is the single biggest efficiency gain available today.

#### Detecting settlement changes incrementally

Don't use a payment's `updated` timestamp to detect settlement events. Assigning a settlement to a payment does not change the payment record. Payments and settlements are separate entities, and this is expected behavior, not a bug. A payment can leave your sync window (if you're windowing on payment `updated`) before it's ever assigned to a settlement, and you'll never see that assignment on the payment side.

Detect changes on the settlement record instead. Two facts about the settlement record's timestamps determine how you do this correctly:

* `GET /Settlements` returns both `created` and `updated` on every settlement record, and `updated` is reliably bumped on every settlement state change (`cleared`, `hold`, `dispatched`, `paid`). So `updated` is a sound change signal.
* **`From` and `To` filter on the settlement's `created` date, not `updated`.** They return settlements *created* in the window. `updated` is sortable (`SortBy=updated`) but is not range filterable today: there is no `updatedFrom` / `updatedTo` or `ModifiedSince` parameter.

Because the change signal (`updated`) and the filter axis (`created`) are different fields, **`From`/`To` on its own is not an updated-since feed.** A settlement created before your window that changes state inside it (for example `cleared` to `paid` several days after it was created, which is exactly the change you're polling for) is excluded, because it was filtered out on `created`. Don't window on `From`/`To` alone and assume you have caught every change.

Use this two part pattern instead:

1. **Discovery of new settlements.** Call `GET /Settlements` with your `MerchantIds` and a `From`/`To` window covering the period since your last sync, adding `SortBy=updated&SortDirection=desc`. This surfaces settlements *created* since you last ran, most recently changed first.
2. **Correctness for slow state changes.** Maintain a working set of the settlement IDs you have seen that are not yet in a terminal state (`status` not `paid`). Each run, re-fetch [GET /Settlements/{id}](/settlement/settlements) for those tracked IDs and compare `updated` (or `status`) against what you last stored. Drop an ID from the set once it reaches `paid`. Reading a settlement by ID is not affected by the `created` window, so this catches a transition no matter how long after creation it happens.

This bounds your work to *new settlements plus the settlements you are still tracking*, independent of total history, and does not miss a slow transition such as a settlement sitting in `hold` for several days before it clears.

If you want the simplest possible version, widen `From` to cover the full period in which a settlement can still change state and re-fetch that whole window each run, deduping on `updated` versus your last sync on your side. This is fine for normal timing, but a settlement can remain in `hold` longer than any fixed window, so pair the widened window with the ID tracking in step 2 for anything not yet `paid`.

#### Two similarly named fields that are not the same thing

| Field                         | Lives on                 | Values                                                                       | What it actually means                                                                  |
| ----------------------------- | ------------------------ | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `paymentSettlementStatusCode` | Payment (Reporting APIs) | `created`, `pending`, `processing`, `completed`, `error`, `initiationFailed` | Whether this payment's own settlement record has been created and processed internally. |
| `status`                      | Settlement record        | `cleared`, `hold`, `dispatched`, `paid`                                      | Where the settlement itself is in the funding and payout lifecycle.                     |

These describe different state machines. A payment can show `paymentSettlementStatusCode` of `completed` while its settlement is still `status` of `hold`. That's not a contradiction. It just means the payment's own settlement record finished processing before the settlement itself finished funding. Don't infer funding or payout state from `paymentSettlementStatusCode`, and don't infer payment level settlement processing from settlement `status`.

#### Settled amount: which field is authoritative

Use the settlement record's `netFundableAmount` as the settled amount for a merchant or period. Don't use the payment's `settleableAmount` as your reconciled total. It reflects a single payment's settleable amount at the payment level, not the actual net amount funded after batch level fees, deposit fees, and adjustments (chargebacks, holdbacks, and so on) are applied. The settlement record is the only place those are all rolled up together. See [Settlements](/settlement/settlements) for the full net fundable amount formula.

#### Known limitation: you can't filter the payments list by settlement or batch

The merchant, partner, and terminal payments endpoints do not support filtering by `settlementId` or `batchId` directly. If you need the payments behind a specific settlement or batch, go the other direction:

* [GET /Settlements/{id}/batches](/settlement/batches) to get the batches in a settlement.
* Read `paymentIds` off each batch.
* Cross reference those IDs against your own payment records, or look them up individually.

Don't build a reconciliation loop that assumes you can query payments by `settlementId`. That filter doesn't exist today.

#### Pagination: zero based across the board

Pagination is zero based across every paged endpoint referenced on this page. `page` (or `Page`) equals `0` returns the first page in the Reporting family ([Partner](/reporting/partner-payments), [Merchant](/reporting/merchant-payments), and [Terminal](/reporting/terminal-payments) Payments) and in the Settlement family ([Settlements](/settlement/settlements), [Batches](/settlement/batches)). There is one shared convention, not two.

If you previously read guidance here suggesting the Reporting family defaults to `page` equals `1`, that was inaccurate and has been corrected. Confirmed directly against the live [Pagination and Sorting](/reporting/payment-history-concepts/pagination-and-sorting) reference, the [Settlements](/settlement/settlements) endpoint reference, and the [Batches](/settlement/batches) endpoint reference.

#### Typical integration flow: nightly portfolio reconciliation

Goal: reconcile settlement activity for every merchant under a partner, once per night, without per merchant calls.

1. Track your last successful sync timestamp, and keep a working set of settlement IDs you have seen that are not yet `paid`.
2. Discovery call: `GET /Settlements` with `MerchantIds` set to your full merchant list and `From`/`To` covering the window since last sync, `SortBy=updated&SortDirection=desc`. Remember `From`/`To` filter on `created`, so this finds newly *created* settlements. Add any new non-terminal settlement IDs to your working set.
3. Change detection: re-fetch [GET /Settlements/{id}](/settlement/settlements) for each ID in your working set and treat a changed `updated` or `status` as a change to process. This catches transitions on settlements created before the current window. Drop an ID from the set once its `status` is `paid`.
4. If you need batch level detail for a changed settlement, call [GET /Settlements/{id}/batches](/settlement/batches).
5. If you need transaction level detail, use `paymentIds` from each batch to cross reference your own records.
6. Use `netFundableAmount` from the settlement record as your reconciled total for that merchant or period.
7. Advance your last sync timestamp only after the run completes successfully.

#### Next steps

* Use [Settlements](/settlement/settlements) for the full settlement field reference and the net fundable amount formula.
* Use [Batches](/settlement/batches) to review grouped payment activity within a settlement.
* Use [Payment Settlement Detail](/settlement/payment-settlement-detail) for the payment level settlement fields and their state progression.
* Use [Reporting](/reporting) and [Pagination and Sorting](/reporting/payment-history-concepts/pagination-and-sorting) for payment history and the Reporting family's pagination contract.
* Use [Settlement data and deposit reconciliation](/faqs-and-troubleshooting/settlement-faqs/settlement-data-and-deposit-reconciliation) for the single deposit reconciliation walkthrough.


# Testing Settlement in the Test Environment

The Bead test environment includes settlement flow simulators for BTC, Lightning, PayPal, Venmo, and Klarna payments. These simulators advance the settlement lifecycle automatically so you can observe and validate the full status progression without waiting for production-equivalent timing.

**How settlement progresses in the test environment**

Settlement for all payment types follows the same lifecycle: payments are grouped into batches, batches roll into settlements, and settlements are funded via ACH. In the test environment, a nightly simulator advances each stage automatically.

The status progression you will observe is:

1. Payments complete and are grouped into an open batch (`batch.status = open`).
2. Batch closes overnight (`batch.status = closed`). A settlement record is created with `status = cleared`.
3. The simulator runs. A simulated ACH event advances the settlement.
4. Settlement status moves to `dispatched`, then `paid`. A deposit record is created and moves to `paid`.

**Simulator schedule**

The simulators run automatically on a nightly schedule. Plan your test flows accordingly:

1. Process test payments.
2. Confirm the batch has closed (`batch.status = closed`) — this happens at the nightly batch close.
3. Check settlement status the following morning — the simulator will have advanced the settlement to `dispatched` or `paid`.

**What to observe when testing**

* Use `GET /Batches` filtered by `Type` (`crypto` for BTC and Lightning, `walletApps` for PayPal, Venmo, and Klarna) to confirm batch status has moved to `closed`.
* Use `GET /Settlements` filtered by `MerchantIds` and `Type` to retrieve the settlement record once the batch has closed.
* Use `GET /Settlements/{id}` to review `status`, `grossFundableAmount`, `totalTransactionFees`, `depositFees`, and `netFundableAmount`.
* Use `GET /Settlements/{id}/deposits` to confirm the deposit record `status` once settlement completes.

Expected end state after a full nightly cycle: batch `status = closed`, settlement `status = paid`, deposit `status = paid`.

**Differences from production**

| Behavior           | Production                                    | Test environment                    |
| ------------------ | --------------------------------------------- | ----------------------------------- |
| Batch close        | Nightly (midnight ET)                         | Nightly (midnight ET)               |
| Settlement funding | Real ACH event                                | Simulated ACH event                 |
| Timing to `paid`   | Vendor-dependent (hours to next business day) | Advances overnight on test schedule |

**Next steps**

* Use [Settlements](/settlement/settlements) to review settlement records and fee breakdowns.
* Use [Batches](/settlement/batches) to track batch status and payment grouping.
* Use [Deposits](/settlement/deposits) to confirm funded movement once settlement completes.
* Use [Payment Settlements](broken://spaces/jL0BeBYhFGcs8tIiuCcV/pages/0I8XtK4ztAK8gab9XUjz) for payment-level settlement detail.
* Use [Merchant Settlements](broken://spaces/jL0BeBYhFGcs8tIiuCcV/pages/zKSBYYX80HC72lJTvqVi) for the merchant-level settlement event view.


# Reporting

The Reporting section provides read-only access to historical payments recorded in the Bead platform. Use it for reconciliation, analytics, dashboards, and operational audits.

Use Reporting when you need lists of payments filtered by merchant, terminal, partner, date range, status, or tender type. For how those payments were grouped, settled, and funded, use the [Settlements](/settlement/settlements) APIs.

> **Building residual or revenue-share reports?** Start with [Settlements](/settlement/settlements) for fee and net fundable amount data, then use [Residual Reporting](/settlement/residual-reporting) for the full integration pattern. Use Reporting APIs to pull the underlying payment activity for the period.

#### When to use Reporting

Use Reporting when you need to:

* Run daily or monthly reconciliation against internal ledgers.
* Generate statements for partners, merchants, or terminals.
* Build dashboards showing recent sales or payment trends.
* Investigate payment discrepancies such as underpaid or overpaid transactions.
* Filter payment history by status, tender type, currency, date range, or reference.

For real-time updates, use webhooks. Reporting complements — it does not replace — webhook notifications.

#### How it works

1. Send a GET request to one of the payment history endpoints.
2. Bead queries a replicated read-only reporting store, updated within seconds of payment and settlement events.
3. Results are returned in pages. Control page size, sort order, date range, and status filters with query parameters.

#### Available endpoints

| Scope    | Path                       | Best for                                                  |
| -------- | -------------------------- | --------------------------------------------------------- |
| Partner  | `/Partners/{id}/payments`  | Portfolio-wide history across all merchants and terminals |
| Merchant | `/Merchants/{id}/payments` | Store-level sales and per-location reports                |
| Terminal | `/Terminals/{id}/payments` | Device-level reconciliation and troubleshooting           |

All three endpoints share the same query parameters, response schema, and field set. See [Pagination and Sorting](/reporting/payment-history-concepts/pagination-and-sorting) for the full parameter reference.

#### Relationship to Settlement

Reporting focuses on payments. Settlement focuses on how those payments were grouped and funded.

Use Reporting when you need payment lists, status filters, and transaction-level fields. Use Settlement when you need fee breakdowns, net fundable amounts, batch groupings, deposit confirmation, or residual reporting data.

A common pattern is to use Reporting to identify the payments for a report or investigation, then use [Settlements](/settlement/settlements) to review the settlement fees and net amounts for that period.

#### Consistency and delay

The reporting database is eventually consistent. Most updates appear within seconds, but large payment volumes or settlement batches can take a minute or two. If a job must not miss records, use fixed time windows and paginate until you receive an empty `data` array.

#### Next steps

* Review [Pagination and Sorting](/reporting/payment-history-concepts/pagination-and-sorting) for query parameter definitions.
* Use [Partner Payments](/reporting/partner-payments) for portfolio-level history.
* Use [Merchant Payments](/reporting/merchant-payments) for store-level history.
* Use [Terminal Payments](/reporting/terminal-payments) for device-level history.
* Use [Settlements](/settlement/settlements) when you need fee totals, adjustments, and net fundable amount.
* Use [Residual Reporting](/settlement/residual-reporting) to assemble fee revenue data for partner reporting.


# Payment History Concepts

### Purpose

This page explains how the Payment History endpoints work, when to use each scope (partner, merchant, terminal), and how to design efficient queries that scale with volume.

Payment History focuses on historical payments. It complements the Settlement APIs, which expose settlement batches and payment level settlement records.

### Real-time versus historical data

* Webhooks and in-flight API responses provide real-time status for individual payments.
* Payment History reads from a replicated reporting store that is updated from payment and settlement events, usually within seconds. It is not intended for second by second monitoring.
* Use the endpoints for reconciliation, dashboards, and audits rather than operational alerting.

If you need to understand how payments were grouped and funded in settlement batches, use the Settlement APIs together with Payment History.

### Relationship to Settlement

Use Payment History when you need:

* Lists of payments for a partner, merchant, or terminal
* Filters based on payment fields such as status, tender type, and time range
* Aggregated views that focus on transactions rather than how they were settled

Use the Settlement APIs when you need:

* Merchant settlement batches that show how payments were grouped and funded
* Payment settlement records that show settlement amounts, currencies, and available times
* Status values that describe the settlement lifecycle for batches and payments

A common integration pattern is:

1. Use Payment History to identify the set of payments that matter for a report or investigation.
2. Use Settlement endpoints to retrieve settlement batches and payment settlement lines for those payments.

### Choosing the right scope

| Scope    | Best when you need…                                    | Endpoint                   |
| -------- | ------------------------------------------------------ | -------------------------- |
| Partner  | Portfolio level dashboards, KPIs, partner statements   | `/Partners/{id}/payments`  |
| Merchant | Store level sales and per location reports             | `/Merchants/{id}/payments` |
| Terminal | Device troubleshooting and in store last payments view | `/Terminals/{id}/payments` |

Merchant scope is most frequently used for reconciliation and settlement reporting. Partner and terminal scopes are helpful for portfolio views and device level troubleshooting.

### Pagination, sorting, and filters

* Always supply `page` and `pageSize`. The default size is fine for UI scroll views. Automated exports often use values around 200.
* Sorting on the created timestamp in descending order is the fastest way to pull “latest payments”.
* Narrow `fromDate` and `toDate` when possible. Smaller windows reduce query cost and speed up responses.
* Status filtering lets you retrieve only exceptions such as `underpaid`, `overpaid`, or `invalid`.

Exact parameter names and types are documented on the Pagination and Sorting page.

### Consistency and delay

* The reporting database is eventually consistent. Most updates appear within a few seconds, but extremely large payment volumes or settlement batches can take a minute or two.
* Treat the returned `total` count as a snapshot. New payments that arrive after your query will increase the total on the next call.
* If you run a job that must not miss records, prefer fixed time windows and page until you receive an empty `data` array.

### Recommended call patterns

| Use case                  | Pattern                                                                                                                                 |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Dashboard cards           | Call once per minute with `sortDirection=desc` and `pageSize=20`.                                                                       |
| End of day reconciliation | Use fixed `fromDate` and `toDate` for the business day. Iterate through pages until `data` is empty.                                    |
| Infinite scroll UI        | Keep `sortDirection=desc`. Move `page` forward until you reach the last page, then extend `fromDate` to the timestamp of the last item. |
| Exception monitoring      | Use `status=underpaid` or `status=overpaid` combined with a one day date window.                                                        |

When reconciliation also involves settlement data, you can use these patterns to pull the relevant payments, then call Settlement endpoints to retrieve batch and settlement line information.

### Error handling

| HTTP status | Typical cause                             | Recovery                                      |
| ----------- | ----------------------------------------- | --------------------------------------------- |
| 400         | Invalid date range or page size too large | Correct the query parameters.                 |
| 404         | Scope identifier not found                | Verify the partner, merchant, or terminal id. |
| 429         | Too many requests                         | Back off and retry with exponential delay.    |

Other errors follow the general error handling guidance in the API.

### Best practices

* Cache the first page in memory for a short period if multiple components read it in quick succession.
* Perform nightly exports in off hours to minimize load on production systems.
* Use partner level history for bulk analytics, then drill down with merchant or terminal scope only when investigating anomalies.
* When working with settlement, use Payment History to identify the payments of interest, then use the Settlement APIs to retrieve merchant settlements and payment settlement records.

Refer to the Pagination and Sorting page for exact parameter definitions, then jump to the endpoint reference that matches your scope.


# Pagination and Sorting

## Pagination and Sorting

`/Partners/{id}/payments`, `/Merchants/{id}/payments`, and `/Terminals/{id}/payments` all use the same pagination, sorting, and filter contract.

Every call is a GET request with parameters supplied in the query string. No request body is required.

**Query parameters**

| Name                          | Type     | Required | Default   | Notes                                                                                                                          |
| ----------------------------- | -------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `from`                        | ISO 8601 | Yes      | —         | Inclusive start timestamp in UTC, e.g. `2025-09-01T00:00:00Z`. Filters on `created`.                                           |
| `to`                          | ISO 8601 | Yes      | —         | Exclusive end timestamp in UTC, e.g. `2025-09-02T00:00:00Z`. Filters on `created`.                                             |
| `page`                        | integer  | Yes      | `0`       | Zero based page number. The first page is `0`, not `1`.                                                                        |
| `pageSize`                    | integer  | Yes      | `50`      | Records per page. Maximum `500`.                                                                                               |
| `sortBy`                      | string   | No       | `created` | Field to sort on.                                                                                                              |
| `sortDirection`               | string   | No       | `desc`    | `asc` or `desc`.                                                                                                               |
| `statusCode`                  | string   | No       | —         | Filter by payment status. See [Payment Statuses](/payments/payment-statuses).                                                  |
| `paymentSettlementStatusCode` | string   | No       | —         | Filter by settlement status. See [Settlement Status Codes](/reference-guide/enumerations-and-schemas/settlement-status-codes). |
| `trackingId`                  | string   | No       | —         | Filter by tracking identifier.                                                                                                 |
| `pageId`                      | string   | No       | —         | Opaque cursor for continuation, echoed from a prior response.                                                                  |
| `reference`                   | string   | No       | —         | Filter by merchant reference, e.g. invoice number.                                                                             |
| `paymentCode`                 | string   | No       | —         | Filter by payment code.                                                                                                        |
| `customerId`                  | string   | No       | —         | Filter by customer ID.                                                                                                         |
| `descriptionSearch`           | string   | No       | —         | Case sensitive text search over the description field.                                                                         |
| `reqCurrencyId`               | integer  | No       | —         | Filter by requested currency ID.                                                                                               |
| `payCurrencyId`               | integer  | No       | —         | Filter by payment currency ID.                                                                                                 |
| `merchantId`                  | string   | No       | —         | Filter to a single merchant. Partner scope only.                                                                               |
| `locationId`                  | string   | No       | —         | Filter to a single location. Partner scope only.                                                                               |

> **Pagination is zero based.** `page=0` returns the first page. Passing `page=1` skips the first page of results, since the request computes `skip = page * pageSize`. This applies across every paged endpoint in the API, not just Payment History.

**Payment status values**

Valid values for `statusCode`:

| Value               | Description                                          |
| ------------------- | ---------------------------------------------------- |
| `created`           | Payment has been created.                            |
| `processing`        | Payment is being processed.                          |
| `completed`         | Payment completed successfully.                      |
| `underpaid`         | Payment received was less than the requested amount. |
| `overpaid`          | Payment received was more than the requested amount. |
| `fullyRefunded`     | Payment has been fully refunded.                     |
| `partiallyRefunded` | Payment has been partially refunded.                 |
| `expired`           | Payment expired before completion.                   |
| `invalid`           | Payment is invalid.                                  |
| `cancelled`         | Payment was cancelled.                               |

**Settlement status values**

Valid values for `paymentSettlementStatusCode`:

| Value              | Description                                               |
| ------------------ | --------------------------------------------------------- |
| `created`          | Payment settlement record has been created.               |
| `pending`          | Payment settlement is pending processing.                 |
| `processing`       | Payment settlement is being processed.                    |
| `completed`        | Payment settlement has completed successfully.            |
| `error`            | Payment settlement encountered an error.                  |
| `initiationFailed` | Payment settlement initiation failed and requires review. |

**Response structure**

```json
{
  "data": [ ... ],
  "total": 27,
  "page": 0
}
```

| Field   | Description                                                |
| ------- | ---------------------------------------------------------- |
| `data`  | Array of payment records for the current page.             |
| `total` | Total number of matching records at the time of the query. |
| `page`  | Current page number returned.                              |

`pageSize` is supplied in the query but is not echoed in the response. Your client should track the value it requested.

**Example request**

```bash
curl -G "https://api.test.devs.beadpay.io/Merchants/mer_4e5a13aa/payments" \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "from=2025-09-01T00:00:00Z" \
  --data-urlencode "to=2025-09-02T00:00:00Z" \
  --data-urlencode "page=0" \
  --data-urlencode "pageSize=50" \
  --data-urlencode "sortBy=created" \
  --data-urlencode "sortDirection=desc" \
  --data-urlencode "statusCode=completed"
```

**Best practices**

* Always supply `from`, `to`, `page`, and `pageSize` on every request.
* Remember that `page` is zero based. Start at `page=0`, not `page=1`.
* Use `sortDirection=desc` on `created` to retrieve the most recent payments first.
* Narrow `from` and `to` as much as possible, smaller windows reduce query cost and improve response time.
* For paginated exports, advance through pages until `data` is an empty array.
* For infinite scroll, advance the `from` window to the timestamp of the last record on the current page rather than incrementing `page` indefinitely.
* Use `pageId` when resuming a previously read window to avoid duplicates.
* Use `statusCode=underpaid` or `statusCode=overpaid` combined with a narrow date window for exception monitoring.
* Treat `total` as a snapshot, new payments arriving after your query will change it on the next call.

**Next steps**

* Use [Partner Payments](/reporting/partner-payments) for portfolio level history.
* Use [Merchant Payments](/reporting/merchant-payments) for store level history.
* Use [Terminal Payments](/reporting/terminal-payments) for device level history.


# Partner Payments

Returns a paginated list of payments across a partner's merchants with filters for time range and other optional fields. Responses use the same schema as Merchant Payments and Terminal Payments and include settlement related fields such as `paymentSettlementStatusCode`, which you can combine with the Settlement APIs for batch and payment settlement details.

**Endpoint**

`GET /Partners/{id}/payments`

**Path parameters**

| Name | Type   | Required | Description                                     |
| ---- | ------ | -------- | ----------------------------------------------- |
| `id` | string | Yes      | The `partnerId` whose payments you want to list |

**Authentication**

```
X-Api-Key: {apiKey}
```

Send your API key in the `X-Api-Key` header. This is the preferred authentication method for Reporting endpoints, including Partner Payments.

Existing integrations that already authenticate with an OAuth 2.0 bearer token can continue to do so, send `Authorization: Bearer <access_token>` instead. New integrations should use `X-Api-Key`.

**Request headers**

| Header    | Value              |
| --------- | ------------------ |
| X-Api-Key | `{apiKey}`         |
| Accept    | `application/json` |

Legacy alternative: `Authorization: Bearer <access_token>` in place of `X-Api-Key`.

**Query parameters**

| Name                          | Type            | Required | Notes                                                                                                                                         |
| ----------------------------- | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `from`                        | string ISO 8601 | Yes      | Inclusive start timestamp in UTC such as `2025-09-01T00:00:00Z`. Filters on `created`.                                                        |
| `to`                          | string ISO 8601 | Yes      | Exclusive end timestamp in UTC such as `2025-09-02T00:00:00Z`. Filters on `created`.                                                          |
| `page`                        | integer         | Yes      | Zero based page number. The first page is `0`, not `1`.                                                                                       |
| `pageSize`                    | integer         | Yes      | Items per page with a maximum of 500                                                                                                          |
| `merchantId`                  | string          | No       | Filter to a single merchant under the partner                                                                                                 |
| `locationId`                  | string          | No       | Filter to a single location under the selected merchant                                                                                       |
| `trackingId`                  | string          | No       | Filter by a specific tracking identifier                                                                                                      |
| `pageId`                      | string          | No       | Optional opaque cursor for continuation when provided by responses                                                                            |
| `reqCurrencyId`               | integer         | No       | Requested currency id filter                                                                                                                  |
| `payCurrencyId`               | integer         | No       | Payment currency id filter                                                                                                                    |
| `customerId`                  | string          | No       | Filter by customer id                                                                                                                         |
| `descriptionSearch`           | string          | No       | Case sensitive text search over the description                                                                                               |
| `paymentCode`                 | string          | No       | Filter by payment code                                                                                                                        |
| `reference`                   | string          | No       | Filter by merchant reference such as invoice number                                                                                           |
| `statusCode`                  | string          | No       | One of `created`, `processing`, `completed`, `underpaid`, `overpaid`, `fullyRefunded`, `partiallyRefunded`, `expired`, `invalid`, `cancelled` |
| `paymentSettlementStatusCode` | string          | No       | One of `created`, `pending`, `processing`, `completed`, `error`, `initiationFailed`. See Settlement status codes in the Reference Guide       |
| `sortBy`                      | string          | No       | Field to sort by when supported for this endpoint                                                                                             |
| `sortDirection`               | string          | No       | `asc` or `desc`                                                                                                                               |

> **Pagination is zero based.** `page=0` returns the first page. Passing `page=1` skips the first page of results, since the request computes `skip = page * pageSize`.

For more about pagination and sorting, see Pagination and Sorting under Payment History Concepts.

**Example request**

```bash
curl -G "https://api.test.devs.beadpay.io/Partners/par_9f2a1cde/payments" \
  -H "X-Api-Key: {apiKey}" \
  --data-urlencode "from=2025-09-01T00:00:00Z" \
  --data-urlencode "to=2025-09-02T00:00:00Z" \
  --data-urlencode "page=0" \
  --data-urlencode "pageSize=50" \
  --data-urlencode "merchantId=mer_4e5a13aa" \
  --data-urlencode "statusCode=completed"
```

**Successful response 200**

```json
{
  "data": [
    {
      "id": "pay_01J8Z1R9K6",
      "created": "2025-09-01T14:22:11.015Z",
      "updated": "2025-09-01T14:22:12.102Z",
      "partnerId": "par_9f2a1cde",
      "merchantId": "mer_4e5a13aa",
      "locationId": "loc_bfdc6a7f",
      "terminalId": "term_12345678",
      "terminalName": "Front Counter 1",
      "paymentCode": "PMT-10452",
      "trackingId": "trk_7c2b0a19",
      "pageId": "pg_2dbb5c",
      "statusCode": "completed",
      "reqCurrencyId": 101,
      "payCurrencyId": 207,
      "amounts": {
        "requested": {
          "inPaymentCurrency": {
            "amount": 25.0,
            "amountPrecision": 2,
            "currencyId": 207
          },
          "inRequestedCurrency": {
            "amount": 25.0,
            "amountPrecision": 2,
            "currencyId": 101
          }
        },
        "paid": {
          "inPaymentCurrency": {
            "amount": 25.0,
            "amountPrecision": 2,
            "currencyId": 207
          }
        },
        "settleableAmount": {
          "amount": 24.6,
          "amountPrecision": 2,
          "currencyId": 101
        },
        "partnerFees": []
      },
      "reference": "INV-10452",
      "description": "Sunglasses",
      "customerId": "cust_abc123",
      "emailReceipt": true,
      "smsReceipt": false,
      "expiration": null,
      "quoteExpiration": null,
      "paymentAddress": null,
      "redirectUrl": null,
      "transactions": [],
      "conversions": [],
      "paymentSettlementStatusCode": "completed",
      "paymentNotifications": [],
      "webhookUrls": [],
      "refundEmail": "customer@example.com",
      "merchantLocation": {
        "id": "loc_bfdc6a7f",
        "name": "Downtown Flagship",
        "address": {
          "address1": "123 Main St",
          "address2": "Suite 200",
          "city": "Springfield",
          "region": "MA",
          "country": "US",
          "postalCode": "01109"
        }
      }
    }
  ],
  "total": 1,
  "page": 0
}
```

#### Field reference

| Field                                                                 | Description                                                                           |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `id`                                                                  | Unique payment identifier                                                             |
| `created`, `updated`                                                  | ISO 8601 timestamps in UTC                                                            |
| `partnerId`, `merchantId`, `locationId`, `terminalId`, `terminalName` | Entities that relate the payment across the partner's estate                          |
| `paymentCode`                                                         | Short code for user facing references                                                 |
| `trackingId`                                                          | Pollable tracking identifier for in flight payments                                   |
| `pageId`                                                              | Opaque continuation id that can be echoed to the `pageId` query parameter             |
| `statusCode`                                                          | Current payment status                                                                |
| `reqCurrencyId`, `payCurrencyId`                                      | Currency identifiers for requested and paid amounts                                   |
| `amounts`                                                             | Object with requested, paid, settleable amount, and partner fees                      |
| `reference`, `description`                                            | Merchant supplied metadata fields                                                     |
| `customerId`                                                          | Merchant supplied customer id when provided                                           |
| `emailReceipt`, `smsReceipt`                                          | Whether a receipt was sent by email or SMS                                            |
| `paymentSettlementStatusCode`                                         | Settlement status for the payment. See Settlement status codes in the Reference Guide |
| `merchantLocation`                                                    | Object with `id`, `name`, and `address` for the location                              |

For field level behaviour that is shared across Partner, Merchant, and Terminal scopes, you can also refer to the Merchant Payments page.

#### Error responses

| Code | Condition                                                                                           |
| ---- | --------------------------------------------------------------------------------------------------- |
| 401  | Missing or invalid `X-Api-Key`, or missing or invalid bearer token when using the legacy OAuth path |
| 403  | Authenticated but not permitted to view payments for this partner                                   |
| 404  | Partner id does not exist or is not visible to your credentials                                     |

#### Best practices

| Action                                                                  | Why                                                                                                                                               |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Use a moving time window such as the last 24 hours for polling          | Reduces payload size and improves performance                                                                                                     |
| Remember that `page` starts at `0`, not `1`                             | Passing `page=1` first skips the actual first page of results                                                                                     |
| Increase `pageSize` for backfills and reduce it for frequent polling    | Balances throughput and latency                                                                                                                   |
| Filter by `merchantId` or `locationId` when the partner has many stores | Speeds up dashboards and reduces response sizes                                                                                                   |
| Use the `updated` timestamp to detect changes                           | Helps you refresh only modified rows                                                                                                              |
| Rely on webhooks for real time state changes                            | Webhooks notify you of status transitions without polling                                                                                         |
| Use Settlement for reconciliation of settled funds                      | Combine `paymentSettlementStatusCode` with the Settlement APIs to understand when and how payments in a partner portfolio were settled and funded |

#### Related endpoints

| Action                   | Endpoint                              |
| ------------------------ | ------------------------------------- |
| Merchant payment history | `GET /Merchants/{id}/payments`        |
| Terminal payment history | `GET /Terminals/{id}/payments`        |
| Webhook event reference  | See Payment Webhooks                  |
| Endpoint discovery       | See Endpoint Index in Reference Guide |

Use Partner Payments for portfolio level analytics and monitoring, then drill down with Merchant Payments and Terminal Payments for store and device level investigations, and use Settlement when you need batch and settlement line details.


# Merchant Payments

**Merchant payment history**

Returns a paginated list of payments for a single merchant with filters for time range and other optional fields. This endpoint does not return settlement lifecycle status on the payment object itself. Use [Settlements](/settlement/settlements), [Batches](/settlement/batches), [Deposits](/settlement/deposits), or [Reporting](/reporting) when you need settlement or reconciliation status for a payment.

> **Known issue:** The payment response currently does not include a `created` timestamp (only `updated`). This is tracked internally as a payload gap (PROD-177) and is expected to be fixed in a future release. Until then, use `updated` for date-based filtering and change detection.

**Endpoint**

`GET /Merchants/{id}/payments`

**Path parameters**

| Name | Type   | Required | Description                                    |
| ---- | ------ | -------- | ---------------------------------------------- |
| `id` | string | Yes      | The merchantId whose payments you want to list |

**Authentication**

```
X-Api-Key: {apiKey}
```

Send your API key in the `X-Api-Key` header. This is the preferred authentication method for Reporting endpoints, including Merchant Payments.

Existing integrations that already authenticate with an OAuth 2.0 bearer token can continue to do so, send `Authorization: Bearer <access_token>` instead. New integrations should use `X-Api-Key`.

**Request headers**

| Header    | Value              |
| --------- | ------------------ |
| X-Api-Key | `{apiKey}`         |
| Accept    | `application/json` |

Legacy alternative: `Authorization: Bearer <access_token>` in place of `X-Api-Key`.

**Query parameters**

| Name                | Type            | Required | Notes                                                                                                                                         |
| ------------------- | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `from`              | string ISO 8601 | Yes      | Inclusive start timestamp in UTC such as `2025-09-01T00:00:00Z`. Filters on `created`.                                                        |
| `to`                | string ISO 8601 | Yes      | Exclusive end timestamp in UTC such as `2025-09-02T00:00:00Z`. Filters on `created`.                                                          |
| `page`              | integer         | Yes      | Zero based page number. The first page is `0`, not `1`.                                                                                       |
| `pageSize`          | integer         | Yes      | Items per page with a maximum of 500                                                                                                          |
| `trackingId`        | string          | No       | Filter by a specific tracking identifier                                                                                                      |
| `pageId`            | string          | No       | Optional opaque cursor for continuation when provided by responses                                                                            |
| `reqCurrencyId`     | integer         | No       | Requested currency id filter                                                                                                                  |
| `payCurrencyId`     | integer         | No       | Payment currency id filter                                                                                                                    |
| `customerId`        | string          | No       | Filter by customer id                                                                                                                         |
| `descriptionSearch` | string          | No       | Case sensitive text search over the description                                                                                               |
| `paymentCode`       | string          | No       | Filter by payment code                                                                                                                        |
| `reference`         | string          | No       | Filter by merchant reference such as invoice number                                                                                           |
| `statusCode`        | string          | No       | One of `created`, `processing`, `completed`, `underpaid`, `overpaid`, `fullyRefunded`, `partiallyRefunded`, `expired`, `invalid`, `cancelled` |
| `sortBy`            | string          | No       | Field to sort by when supported for this endpoint                                                                                             |
| `sortDirection`     | string          | No       | `asc` or `desc`                                                                                                                               |

> **Pagination is zero based.** `page=0` returns the first page. Passing `page=1` skips the first page of results, since the request computes `skip = page * pageSize`.

> **Note:** `paymentSettlementStatusCode` is not currently returned on or filterable against this endpoint's payment objects — see Known issue above. It has been removed as a query parameter pending a decision on whether it will be added to the payment DTO.

**Example request**

```bash
curl -G "https://api.test.devs.beadpay.io/Merchants/mer_4e5a13aa/payments" \
-H "X-Api-Key: {apiKey}" \
--data-urlencode "from=2025-09-01T00:00:00Z" \
--data-urlencode "to=2025-09-02T00:00:00Z" \
--data-urlencode "page=0" \
--data-urlencode "pageSize=50" \
--data-urlencode "statusCode=completed"
```

**Successful response 200**

```json
{
"data": [
{
"id": "pay_01J8Z1R9K6",
"updated": "2025-09-01T14:22:12.102Z",
"terminalId": "term_12345678",
"terminalName": "Front Counter 1",
"paymentCode": "PMT-10452",
"trackingId": "trk_7c2b0a19",
"pageId": "pg_2dbb5c",
"statusCode": "completed",
"reqCurrencyId": 101,
"payCurrencyId": 207,
"amounts": {
"requested": {
"inPaymentCurrency": { "amount": 25.0, "amountPrecision": 2, "currencyId": 207 },
"inRequestedCurrency": { "amount": 25.0, "amountPrecision": 2, "currencyId": 101 }
},
"paid": {
"inPaymentCurrency": { "amount": 25.0, "amountPrecision": 2, "currencyId": 207 }
},
"settleableAmount": { "amount": 24.6, "amountPrecision": 2, "currencyId": 101 },
"partnerFees": []
},
"reference": "INV-10452",
"description": "Sunglasses",
"customerId": "cust_abc123",
"emailReceipt": true,
"smsReceipt": false,
"expiration": null,
"quoteExpiration": null,
"paymentAddress": null,
"redirectUrl": null,
"transactions": [],
"conversions": [],
"paymentNotifications": [],
"webhookUrls": [],
"refundEmail": "customer@example.com",
"merchantLocation": {
"id": "loc_bfdc6a7f",
"name": "Downtown Flagship",
"address": {
"address1": "123 Main St",
"address2": "Suite 200",
"city": "Springfield",
"region": "MA",
"country": "US",
"postalCode": "01109"
}
}
}
],
"total": 1,
"page": 0
}
```

**Field reference**

| Field                            | Description                                                                                             |
| -------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `id`                             | Unique payment identifier                                                                               |
| `updated`                        | ISO 8601 timestamp in UTC. Note: `created` is not currently returned — see Known issue above (PROD-177) |
| `terminalId`, `terminalName`     | Terminal that originated the payment                                                                    |
| `paymentCode`                    | Short code for user facing references                                                                   |
| `trackingId`                     | Pollable tracking identifier for in flight payments                                                     |
| `pageId`                         | Opaque continuation id that can be echoed to the `pageId` query parameter                               |
| `statusCode`                     | Current payment status                                                                                  |
| `reqCurrencyId`, `payCurrencyId` | Currency identifiers for requested and paid amounts                                                     |
| `amounts`                        | Object with requested, paid, settleable amount, and partner fees                                        |
| `reference`, `description`       | Merchant supplied metadata fields                                                                       |
| `customerId`                     | Merchant supplied customer id when provided                                                             |
| `emailReceipt`, `smsReceipt`     | Whether a receipt was sent by email or SMS                                                              |
| `merchantLocation`               | Object with `id`, `name`, and `address` for the location                                                |

> `paymentSettlementStatusCode` is not present on this response, including for completed and fully settled payments. For settlement status, use [Settlements](/settlement/settlements), [Batches](/settlement/batches), [Deposits](/settlement/deposits), or [Reporting](/reporting).

**Error responses**

| Code | Condition                                                                                           |
| ---- | --------------------------------------------------------------------------------------------------- |
| 401  | Missing or invalid `X-Api-Key`, or missing or invalid bearer token when using the legacy OAuth path |
| 403  | Authenticated but not permitted to view payments for this merchant                                  |

**Best practices**

| Action                                                                               | Why                                                                                                                                                                                                                                                                           |
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Use a moving time window such as the last 24 hours for polling                       | Reduces payload size and improves performance                                                                                                                                                                                                                                 |
| Remember that `page` starts at `0`, not `1`                                          | Passing `page=1` first skips the actual first page of results                                                                                                                                                                                                                 |
| Increase `pageSize` for backfills and reduce it for frequent polling                 | Balances throughput and latency                                                                                                                                                                                                                                               |
| Filter by `pageId` when resuming a previously read window                            | Avoids duplicates when paginating through large ranges                                                                                                                                                                                                                        |
| Use the `updated` timestamp to detect changes                                        | Helps you refresh only modified rows                                                                                                                                                                                                                                          |
| Rely on [payment webhooks](/payments/payment-webhooks) for real time state changes   | Webhooks notify you of status transitions without polling                                                                                                                                                                                                                     |
| Use [Settlement APIs](/settlement) when you need settlement or reconciliation status | The payment response does not expose settlement lifecycle fields — use [Settlements](/settlement/settlements), [Batches](/settlement/batches), [Deposits](/settlement/deposits), or [Reporting](/reporting) to see settlement status and how payments were grouped and funded |


# Terminal Payments

Retrieve historical payments processed by a single terminal. Use this endpoint for device level reconciliation, in store reporting, or quick troubleshooting at the terminal level.

Responses follow the same schema as Partner Payments and Merchant Payments and include settlement related fields such as `paymentSettlementStatusCode`. You can use these fields together with the Settlement APIs when you need batch or payment settlement details.

#### Endpoint

`GET /Terminals/{id}/payments`

#### Path parameters

| Name | Type   | Required | Description                        |
| ---- | ------ | -------- | ---------------------------------- |
| id   | string | Yes      | The `terminalId` you want to query |

#### Authentication

```
X-Api-Key: {apiKey}
```

Send your API key in the `X-Api-Key` header. This is the preferred authentication method for Reporting endpoints, including Terminal Payments.

Existing integrations that already authenticate with an OAuth 2.0 bearer token can continue to do so, send `Authorization: Bearer <access_token>` instead. New integrations should use `X-Api-Key`.

#### Request headers

| Header    | Value              |
| --------- | ------------------ |
| X-Api-Key | `{apiKey}`         |
| Accept    | `application/json` |

Legacy alternative: `Authorization: Bearer <access_token>` in place of `X-Api-Key`.

#### Query parameters

Terminal Payments supports the same pagination, sorting, and filter options as Partner Payments and Merchant Payments.

| Name                        | Type            | Required | Notes                                                                                                                                         |
| --------------------------- | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| from                        | string ISO 8601 | Yes      | Inclusive start timestamp in UTC such as `2025-09-01T00:00:00Z`. Filters on `created`.                                                        |
| to                          | string ISO 8601 | Yes      | Exclusive end timestamp in UTC such as `2025-09-02T00:00:00Z`. Filters on `created`.                                                          |
| page                        | integer         | Yes      | Zero based page number. The first page is `0`, not `1`.                                                                                       |
| pageSize                    | integer         | Yes      | Items per page with a maximum of 500                                                                                                          |
| trackingId                  | string          | No       | Filter by a specific tracking identifier                                                                                                      |
| pageId                      | string          | No       | Optional opaque cursor for continuation when provided by responses                                                                            |
| reqCurrencyId               | integer         | No       | Requested currency id filter                                                                                                                  |
| payCurrencyId               | integer         | No       | Payment currency id filter                                                                                                                    |
| customerId                  | string          | No       | Filter by customer id                                                                                                                         |
| descriptionSearch           | string          | No       | Case sensitive text search over the description                                                                                               |
| paymentCode                 | string          | No       | Filter by payment code                                                                                                                        |
| reference                   | string          | No       | Filter by merchant reference such as invoice number                                                                                           |
| statusCode                  | string          | No       | One of `created`, `processing`, `completed`, `underpaid`, `overpaid`, `fullyRefunded`, `partiallyRefunded`, `expired`, `invalid`, `cancelled` |
| paymentSettlementStatusCode | string          | No       | One of `created`, `pending`, `processing`, `completed`, `error`, `initiationFailed`. See Settlement status codes in the Reference Guide       |
| sortBy                      | string          | No       | Field to sort by when supported for this endpoint                                                                                             |
| sortDirection               | string          | No       | `asc` or `desc`                                                                                                                               |

> **Pagination is zero based.** `page=0` returns the first page. Passing `page=1` skips the first page of results, since the request computes `skip = page * pageSize`.

Exact parameter definitions are also described on the Pagination and Sorting page.

#### Example request

```bash
curl -G "https://api.test.devs.beadpay.io/Terminals/term_12345678/payments" \
  -H "X-Api-Key: {apiKey}" \
  --data-urlencode "from=2025-09-01T00:00:00Z" \
  --data-urlencode "to=2025-09-02T00:00:00Z" \
  --data-urlencode "page=0" \
  --data-urlencode "pageSize=50" \
  --data-urlencode "statusCode=completed"
```

#### Successful response 200

```json
{
  "data": [
    {
      "id": "pay_01J8Z1R9K6",
      "created": "2025-09-01T14:22:11.015Z",
      "updated": "2025-09-01T14:22:12.102Z",
      "terminalId": "term_12345678",
      "terminalName": "Front Counter 1",
      "paymentCode": "PMT-10452",
      "trackingId": "trk_7c2b0a19",
      "pageId": "pg_2dbb5c",
      "statusCode": "completed",
      "reqCurrencyId": 101,
      "payCurrencyId": 207,
      "amounts": {
        "requested": {
          "inPaymentCurrency": { "amount": 25.0, "amountPrecision": 2, "currencyId": 207 },
          "inRequestedCurrency": { "amount": 25.0, "amountPrecision": 2, "currencyId": 101 }
        },
        "paid": {
          "inPaymentCurrency": { "amount": 25.0, "amountPrecision": 2, "currencyId": 207 }
        },
        "settleableAmount": { "amount": 24.6, "amountPrecision": 2, "currencyId": 101 },
        "partnerFees": []
      },
      "reference": "INV-10452",
      "description": "Sunglasses",
      "customerId": "cust_abc123",
      "emailReceipt": true,
      "smsReceipt": false,
      "expiration": null,
      "quoteExpiration": null,
      "paymentAddress": null,
      "redirectUrl": null,
      "transactions": [],
      "conversions": [],
      "paymentSettlementStatusCode": "completed",
      "paymentNotifications": [],
      "webhookUrls": [],
      "refundEmail": "customer@example.com",
      "merchantLocation": {
        "id": "loc_bfdc6a7f",
        "name": "Downtown Flagship",
        "address": {
          "address1": "123 Main St",
          "address2": "Suite 200",
          "city": "Springfield",
          "region": "MA",
          "country": "US",
          "postalCode": "01109"
        }
      }
    }
  ],
  "total": 1,
  "page": 0
}
```

This example mirrors the structure used for Partner Payments and Merchant Payments. The actual data will vary based on your environment and test activity.

#### Field reference

Terminal payment records use the same fields as Partner Payments and Merchant Payments.

Key fields include:

* `id`
* `created`, `updated`
* `terminalId`, `terminalName`
* `paymentCode`, `trackingId`, `pageId`
* `statusCode`
* `reqCurrencyId`, `payCurrencyId`
* `amounts` including requested, paid, `settleableAmount`, and partner fees
* `reference`, `description`, `customerId`
* `emailReceipt`, `smsReceipt`
* `paymentSettlementStatusCode`
  * Settlement status for the payment. See Settlement status codes in the Reference Guide
* `merchantLocation` with `id`, `name`, and `address`

For a full field by field description, see the Merchant Payments page. Terminal payments use the same schema, with terminal centric filtering.

#### Error responses

| Code | Condition                                                                                           |
| ---- | --------------------------------------------------------------------------------------------------- |
| 401  | Missing or invalid `X-Api-Key`, or missing or invalid bearer token when using the legacy OAuth path |
| 403  | Authenticated but not permitted to view payments for this terminal                                  |
| 404  | Terminal id does not exist or is not visible to your credentials                                    |

#### Best practices

Recommended patterns when using Terminal Payments

* Remember that `page` starts at `0`, not `1`. Passing `page=1` first skips the actual first page of results.
* Use a narrow time window, such as the last 24 hours, when polling for recent terminal activity
* Increase `pageSize` for one time backfills and reduce it for frequent dashboards or troubleshooting views
* Use `sortDirection=desc` on created time to see the most recent payments first in device diagnostics
* Combine terminal scope with partner or merchant scope when investigating issues that may affect multiple devices
* Use `paymentSettlementStatusCode` together with the Settlement APIs to see how terminal level payments flow into merchant settlement batches and funding

For portfolio level and store level views, see Partner Payments and Merchant Payments.


# Reference Guide

This guide defines shared patterns used across Bead APIs, including authentication, headers, pagination, errors, field formats, and discovery. Use it alongside the Endpoint Index and the product area guides.

### Quick links

| Topic                      | Where                                                                                                         |
| -------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Environments and base URLs | This page, [Environments at a glance](#environments-at-a-glance)                                              |
| Download OpenAPI           | [Download OpenAPI](/reference-guide/endpoint-index/download-openapi)                                          |
| Endpoint Index             | This page, [Endpoint Index](/reference-guide/endpoint-index)                                                  |
| Authentication             | This page, [Authentication at a glance](#authentication-at-a-glance)                                          |
| Tender Types               | [Payments](/payments), [Tender Types](/reference-guide/enumerations-and-schemas/tender-types)                 |
| Payment History concepts   | [Reporting](/reporting), [Payment History](/reporting/payment-history-concepts)                               |
| Rate limits and retries    | This page, [Rate limits and retries](/reference-guide/core-concepts/rate-limits-and-throttling)               |
| Error model                | This page, [Error model](/reference-guide/core-concepts/error-codes)                                          |
| Webhook Event Reference    | [Payments](/payments), [Webhook Event Reference](/reference-guide/operational-guides/webhook-event-reference) |

### Environments at a glance

| Environment | Purpose                               | REST API base URL                  | OAuth token URL\*                                                          |
| ----------- | ------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------- |
| Sandbox     | Functional testing and integration QA | `https://api.test.devs.beadpay.io` | `https://identity.beadpay.io/realms/{realm}/protocol/openid-connect/token` |
| Production  | Live merchant traffic and settlement  | `https://api.beadpay.io`           | `https://identity.beadpay.io/realms/{realm}/protocol/openid-connect/token` |

\* Only for endpoint families that explicitly require OAuth.

#### Notes

* Use the correct realm per environment. Your Bead contact will confirm the realm values you should use for Sandbox and Production.
* Keep base URLs and credentials in configuration so switching environments is a deploy-time change, not a code change.
* Keep Sandbox and Production credentials separate.

### Authentication at a glance

Bead supports multiple authentication methods depending on the API family you are calling.

| API family                                            | Preferred auth   | Required header                        | Notes                                                                             |
| ----------------------------------------------------- | ---------------- | -------------------------------------- | --------------------------------------------------------------------------------- |
| Payments                                              | Terminal API key | `X-Api-Key: {apiKey}`                  | Use the full `apiKey` value. `maskedApiKey` is not usable.                        |
| Onboarding                                            | API key          | `X-Api-Key: {apiKey}`                  | Use API key authentication for onboarding requests.                               |
| Entity Management and related admin/entity operations | API key          | `X-Api-Key: {apiKey}`                  | Use API key authentication where the product-area docs show `X-Api-Key`.          |
| Endpoint families that explicitly require OAuth       | OAuth 2.0        | `Authorization: Bearer {access_token}` | Only use OAuth where the endpoint family or product-area page explicitly says so. |

### Standard headers

Use these headers unless an endpoint states otherwise.

| Header         | Value                                            |
| -------------- | ------------------------------------------------ |
| `Content-Type` | `application/json` for requests with JSON bodies |
| `Accept`       | `application/json`                               |

### API key example headers

| Header         | Value              |
| -------------- | ------------------ |
| `X-Api-Key`    | `{apiKey}`         |
| `Content-Type` | `application/json` |
| `Accept`       | `application/json` |

#### Example curl

```bash
curl -s -X GET "https://api.test.devs.beadpay.io/merchant-onboarding/applications/{applicationId}" \
  -H "Accept: application/json" \
  -H "X-Api-Key: {apiKey}"
```

### OAuth guidance

Some Bead endpoint families may still require OAuth. When OAuth applies, follow the authentication instructions on that specific product-area or endpoint-family page.

#### Token endpoint format

`POST https://identity.beadpay.io/realms/{realm}/protocol/openid-connect/token`

#### Standard OAuth request header

`Content-Type: application/x-www-form-urlencoded`

#### Bearer token header

`Authorization: Bearer {access_token}`

#### Important note

Do not assume OAuth is required for every Bead API. Use the authentication method documented for the product area you are integrating with.

### Conventions

Shared rules for timestamps, identifiers, pagination, errors, and discovery.

#### Timestamps

| Topic    | Convention                                            |
| -------- | ----------------------------------------------------- |
| Format   | ISO 8601 in UTC with `Z` suffix                       |
| Examples | `2025-09-02T16:22:11Z`, `2025-09-02T16:22:11.015Z`    |
| Fields   | `created`, `updated`, and similar date fields use UTC |

#### Identifiers and casing

| Topic            | Convention                                                                                   |
| ---------------- | -------------------------------------------------------------------------------------------- |
| IDs              | Treat IDs as opaque strings                                                                  |
| Case sensitivity | Enum values and IDs are case sensitive                                                       |
| Immutability     | IDs and server-managed timestamps are read-only unless an endpoint explicitly says otherwise |

#### Pagination

Many list endpoints use `page` and `pageSize`.

| Parameter  | Type    | Notes                                                                 |
| ---------- | ------- | --------------------------------------------------------------------- |
| `page`     | integer | Follow the numbering and defaults documented on the specific endpoint |
| `pageSize` | integer | Page size                                                             |

#### Pagination notes

* Some endpoints return paginated envelopes.
* Some list endpoints return a bare array when the full set is expected to be small.
* Always follow the specific endpoint schema and examples for paging behavior.

#### Error model

Error responses follow the schema documented for the endpoint or API family.

General guidance:

* Surface the human-readable message to end users when appropriate.
* Log trace or correlation identifiers when present.
* Preserve field-level validation details when available.

#### HTTP status codes

| Status | When                                 |
| ------ | ------------------------------------ |
| `200`  | Successful read or update            |
| `201`  | Resource created                     |
| `202`  | Accepted for asynchronous processing |
| `204`  | Successful delete with no body       |
| `400`  | Invalid input or request shape       |
| `401`  | Missing or invalid credential        |
| `403`  | Authenticated but not permitted      |
| `404`  | Resource not found                   |
| `409`  | Duplicate or state conflict          |
| `422`  | Business rule failure                |
| `500`  | Unexpected server error              |

### Amounts and currencies

| Topic          | Convention                                                                                                                        |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Numeric fields | Use JSON numbers unless an endpoint explicitly documents otherwise                                                                |
| Precision      | Follow the precision documented for the field or tender                                                                           |
| Currency       | If a field requires a currency, it is documented next to that field; otherwise it may be implied by the merchant, rail, or tender |

### Tender types and enums

| Topic               | Convention                                                                                    |
| ------------------- | --------------------------------------------------------------------------------------------- |
| Tender types        | Use the exact strings shown on the Tender Types pages                                         |
| Case and separators | Values are case sensitive. Copy them exactly as shown                                         |
| Future expansion    | New enum values can appear without a version change. Ignore unknown values when safe to do so |

### Rate limits and retries

| Topic       | Guidance                                                                  |
| ----------- | ------------------------------------------------------------------------- |
| Retries     | Use exponential backoff on `429` and transient `5xx` responses            |
| Headers     | Respect any retry, quota, or remaining-limit headers if returned          |
| Idempotency | If an endpoint supports idempotency keys, that endpoint page will note it |

### Download OpenAPI

Use the download page to retrieve the latest OpenAPI specification in JSON format.

The OpenAPI file is useful for:

* generating clients
* validating requests and responses
* aligning internal tooling with the current public schema
* reviewing available paths, request bodies, and response models

### Discovery and next steps

| Action                                    | Where                                                                                                         |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Explore paths and methods                 | [Reference Guide](/reference-guide), [Endpoint Index](/reference-guide/endpoint-index)                        |
| Download the OpenAPI specification        | [Download OpenAPI](/reference-guide/endpoint-index/download-openapi)                                          |
| Review webhook event types and signatures | [Payments](/payments), [Webhook Event Reference](/reference-guide/operational-guides/webhook-event-reference) |
| Learn payment history filters and paging  | [Reporting](/reporting), [Payment History Concepts](/reporting/payment-history-concepts)                      |
| See changes by area and date              | [Changelog](/changelog)                                                                                       |


# Conventions


# Identifiers and Keys

### Identifiers and Keys

Integrations rely on a small set of identifiers and secret keys that show up across many endpoints. The formatting rules below help integrators catch copy paste errors early and reduce failed requests caused by malformed values.

Identifiers and keys should be treated as opaque strings. Do not infer meaning from their structure.

#### Identifier formatting

Many Bead objects are referenced by an `id` value. Examples include:

* `merchantId`
* `terminalId`
* `applicationId`

These identifiers commonly appear in API request bodies and path parameters.

**Format**

Identifiers use this general format:

* 24 characters
* Hex characters only
* Case-insensitive for format checks

Examples (slightly modified for documentation and not usable as real IDs):

```
664c5e3b0517b0a8a6321c9b
699764b03b43eb9fd55de045
```

**Recommended format validation**

Use this regex to validate identifier formatting:

```regex
^[0-9a-fA-F]{24}$
```

If an identifier does not match this format, return a client-side error before sending the API request, or handle the API response as a validation error.

Format validation only confirms that the identifier is well formed. It does not confirm the identifier exists or that the caller has access to it.

#### Header keys used to facilitate payments

To initiate a payment request, integrators generally need three pieces of information:

* `merchantId`
* `terminalId`
* `X-Api-Key` header value

The `merchantId` and `terminalId` follow the identifier format described above.

The `X-Api-Key` is a header key that should be treated as a secret. It should never be logged, stored in client-side code, or shared in screenshots.

**Terminal X-Api-Key format**

A terminal API key commonly uses a three-part, dot-delimited format:

```
<type>.<encoded-part>.<signature>
```

Example (slightly modified for documentation and not usable as a real key):

```
term.Njk5NzY0YjAzYjQzZWI5ZmQ1NWRlMDQ0.CUa6LX8FH-kB1oLQ6C9vKP18VwojKqRqkw0MDU1YsYpxW7J5rMoMYsOPakVT2WCs
```

**Recommended format validation**

If you want to do lightweight format validation on the key:

* Must contain exactly 3 segments separated by `.`
* Segment 1 should be lowercase letters
* Segments 2 and 3 should contain only `A–Z`, `a–z`, `0–9`, `_`, `-`

Regex:

```regex
^[a-z]+\.([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]+)$
```

Format validation does not confirm the key is active or authorized. All real validation happens server-side.

#### Partner onboarding key formats

Some onboarding and operational workflows may use keys with a similar dot-delimited structure, but with a different prefix and an additional environment marker.

Example (slightly modified for documentation and not usable as a real key):

```
part.live.Njk2ZTJlM2JkZjBhMDU4ZWRiNTlmN2I3.Jbe4-HDv0FNNXUtpuPhrKM90fsyeQNoXXIduV699VSRRFkdRRa3e1s4SDvF5kkpc
```

**Format**

Partner onboarding keys commonly follow this general structure:

```
<prefix>.<environment>.<encoded-part>.<signature>
```

Example components:

* Prefix: `part`
* Environment: `live` (other environments may exist)
* Encoded part: opaque value
* Signature: opaque value

**Recommended format validation**

* Must contain exactly 4 segments separated by `.`
* Segment 1 and 2 should be lowercase letters
* Segments 3 and 4 should contain only `A–Z`, `a–z`, `0–9`, `_`, `-`

Regex:

```regex
^[a-z]+\.[a-z]+\.([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]+)$
```

#### Security and handling guidance

* Treat all keys as secrets
* Do not log full keys
* Do not commit keys to source control
* Store keys in secure server-side configuration (secrets manager, environment variables)
* Rotate keys immediately if they are exposed

#### Common validation errors

If you receive a 400-level validation error, check:

* Identifier values are present and match `^[0-9a-fA-F]{24}$`
* Keys have the correct number of dot-delimited segments
* No leading or trailing whitespace (copy/paste issues)
* The correct key is being used for the intended environment (test vs live)

If issues persist after confirming formatting, contact Bead Support with the request ID and the relevant entity IDs (do not send full keys in email or chat).


# Core Concepts

This page orients you to the cross cutting rules that apply to every Bead API. Each topic has its own detail page. The summaries below tell you when to click through.

### **Environments and base URLs**

Sandbox and production hosts, ports, TLS requirements, health check endpoints, and how to switch between environments without code changes.

See the Environments & Base URLs page in the Reference Guide for the full table of:

* REST API base URLs for Sandbox and Production
* OAuth token URLs for each realm (`nonprod` and `prod`)
* Health check endpoints and connectivity notes

### **Authentication and OAuth 2.0**

How to obtain and refresh access tokens for the Bead APIs.

The Authentication page covers:

* OAuth 2 password grant flow
* Realms used by Bead (`nonprod` for Sandbox and other non production environments, `prod` for Production)
* Which client to use (`bead-terminal` for Payments, `bead-integrator` for Onboarding and Entity Management)
* Token response fields, expiry, and refresh token usage
* Example curl requests and common error conditions

Use this when you are wiring up token acquisition or troubleshooting authorization failures.

### **Error codes**

Standard error response format and how to decide when to retry versus fail fast.

The Error Codes page describes:

* The standard JSON error object shape
* How HTTP status codes map to fault categories
* Which errors are safe to retry, such as transient `5xx` and `429` with backoff
* Examples of validation errors (`400`), authentication issues (`401` or `403`), and not found conditions (`404`)

Use this when building error handling, logging, and alerting for your integration.


# Authentication & OAuth 2.0

> **Most integrators should not need this page.** The preferred authentication method across current Bead API families, including Payments, Onboarding, Reporting, Settlement, and Entity Management, is an API key sent in the `X-Api-Key` header. See [Authentication](/authentication) for the current, API key first guidance and examples.
>
> This page documents the OAuth 2.0 password grant model. It applies to existing integrations that were provisioned with OAuth credentials before API key authentication was available, and to any endpoint that specifically requires OAuth. If you are starting a new integration and have not been given OAuth client credentials, use `X-Api-Key` instead and you can skip this page entirely.

Existing OAuth integrations obtain an access token, then send it on every request. Use separate credentials for Test and Production.

#### Token endpoints

| Environment     | OAuth token URL                                                            |
| --------------- | -------------------------------------------------------------------------- |
| Test / Non-prod | `https://identity.beadpay.io/realms/nonprod/protocol/openid-connect/token` |
| Production      | `https://identity.beadpay.io/realms/prod/protocol/openid-connect/token`    |

Token endpoint pattern used for all realms:

```
{OIDC_BASE}/protocol/openid-connect/token
```

#### Headers

| Header        | Value                                       |
| ------------- | ------------------------------------------- |
| Authorization | `Bearer <access_token>`                     |
| Content-Type  | `application/json` for requests with bodies |
| Accept        | `application/json`                          |

#### Integration identities at a glance

These describe the legacy OAuth models still supported for existing integrations. New integrations should use API key authentication and can skip this table.

| Model                                   | Client ID       | Credential subject                                         | Grant type     | Token cache key                                          | Latency posture                                  |
| --------------------------------------- | --------------- | ---------------------------------------------------------- | -------------- | -------------------------------------------------------- | ------------------------------------------------ |
| Payments (legacy)                       | Payments client | Terminal username and password such as <term@merchant.com> | Password grant | `terminalId` or `clientId` + `merchantId` + `terminalId` | Optimize for low latency and high volume         |
| Boarding and Entity Management (legacy) | Boarding client | Single integrator username and password                    | Password grant | `clientId`, usually a single token is sufficient         | Latency is secondary and auditability is primary |

#### Token types and lifetimes

| Token         | Typical lifetime | How it is used                                                   |
| ------------- | ---------------- | ---------------------------------------------------------------- |
| Access token  | 3600 seconds     | Sent on every API call in `Authorization: Bearer <access_token>` |
| Refresh token | 36000 seconds    | Exchanged for a new access token and a new refresh token         |

Lifetimes can vary by environment and client. Always honor `expires_in` and `refresh_expires_in` returned by the token endpoint.

#### Standard policy for both legacy models

* Cache the access token and refresh two to five minutes before expiry
* If an API call returns 401 Unauthorized, perform one refresh and retry once
* Always store the newest refresh token that the identity server returns
* If refresh fails with `invalid_grant` or the refresh token is expired, perform a full sign in
* Keep tokens on the server only, do not log tokens, encrypt at rest if persisted
* In multi-process or multi-worker apps, ensure only one worker performs the refresh and others reuse the result

#### Payments integration (legacy OAuth path)

Goal: fast and reliable requests at scale without authenticating on the hot path.

* Do not call the token endpoint per transaction
* Maintain a shared token cache keyed by `terminalId` or by `clientId` + `merchantId` + `terminalId`
* Refresh proactively with a small buffer and jitter so many tokens do not refresh at the same second
* Use a leader process to perform the refresh and fan out the new pair to workers
* Warm the cache for your busiest terminals at service start or during off peak windows
* Only do a full sign in if refresh fails

New Payments integrations should use `X-Api-Key` instead. See [Authentication](/authentication).

#### Boarding and entity management (legacy OAuth path)

Goal: safe and auditable operations where latency is less critical.

* Use the same refresh and 401 retry rules with a larger pre-expiry buffer
* A single service level token is usually sufficient for administrative workloads
* Emphasize least privilege scopes and clear audit logging of who did what and when

#### Why use refresh tokens

Security and reliability:

* Renew access without resending primary credentials
* Identity can revoke a refresh token family to cut off access quickly
* Each refresh issues a fresh access token so roles and scopes remain current

Performance:

* One light refresh call is faster and cheaper than a full sign in
* Fewer round trips and fewer failures under load

#### Minimal refresh decision logic

Proactive, before calling an API:

* If `now >= issued_at + expires_in − buffer` then refresh
* Otherwise use the cached access token. Suggested buffer is 120 to 300 seconds with small random jitter in high volume systems

Reactive fallback on failure:

* If a request returns 401 and you have not just refreshed, perform one refresh and retry once
* If it still fails, perform a full sign in and alert

#### Grant types

Choose the grant type you were provisioned for. Fields use standard OpenID Connect names.

**Password grant**

```
curl -X POST "https://identity.beadpay.io/realms/nonprod/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "client_id=<clientId>" \
  -d "username=<username>" \
  -d "password=<password>" \
  -d "scope=openid profile email"
```

To target Production, change `nonprod` to `prod`.

**Refresh token grant**

```
curl -X POST "https://identity.beadpay.io/realms/nonprod/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "client_id=<clientId>" \
  -d "refresh_token=<refreshToken>"
```

Swap `nonprod` to `prod` to refresh in Production.

**Token response**

```json
{
  "access_token": "<jwt>",
  "expires_in": 3600,
  "token_type": "Bearer",
  "refresh_token": "<refreshToken-when-present>",
  "scope": "openid profile email"
}
```

#### Using tokens

Send the access token on every API call in the Authorization header as `Bearer <access_token>`.

#### Next steps

* See [Authentication](/authentication) for current, API key first authentication guidance across all API families.
* See [Where do I find the base URLs, realm, and client IDs?](/faqs-and-troubleshooting/authentication-faqs/where-do-i-find-the-base-urls-realm-and-client-ids) if you are on the legacy OAuth path and need realm or client ID values.


# Crypto and Wallet Concepts for Integrators

This page gives integrators a practical mental model for how Bead uses crypto assets and wallets. It explains:

* How the hosted payment page works for crypto
* What customers need in their wallet
* How network fees work
* High level ways customers get funds into a wallet

For step by step wallet instructions, customers should use the help center for their wallet provider. This page is focused on system behavior, not consumer support.

#### How crypto payments work with Bead

At a high level a crypto payment looks like this:

1. Your system creates a payment with Bead for a specific asset and network.
2. Bead generates a unique address and amount for that payment on that network.
3. The hosted payment page shows the customer the asset, the network, the exact amount to send, and a QR code that encodes the address, network, and amount.
4. The customer opens a compatible wallet, scans the code or copies the details, and confirms the send.
5. Bead monitors the network and updates the payment status as confirmations occur.

Key point: Bead is giving the wallet a precise set of instructions. The main failure cases occur when the customer selects the wrong asset, the wrong network, or does not have enough balance to cover network fees.

#### Asset types (USDC, Bitcoin, and Lightning)

Bead supports multiple asset types that behave differently at the network level.

For USDC on smart contract networks:

* USDC on Base
  * Payment token: USDC on Base
  * Fee token: ETH on Base
* USDC on Solana
  * Payment token: USDC on Solana
  * Fee token: SOL on Solana

For Bitcoin based payments:

* Bitcoin on-chain
  * Payment asset and fee asset are both BTC.
  * There is no separate fee token. Network fees are deducted in BTC as part of the transaction.
* Bitcoin Lightning
  * Uses payment channels managed by Lightning capable wallets.
  * Routing and fees are handled within the Lightning network and the user’s Lightning wallet.

The Compatible Crypto Wallets page describes which wallets and networks are supported for USDC, Bitcoin on-chain, and Bitcoin Lightning, and how payment and fee behavior works for each combination.

#### Networks and compatible assets

The hosted payment page always specifies a network for the payment, such as Base, Solana, Bitcoin mainnet, or Bitcoin Lightning.

Wallets often allow the same symbol or brand to exist on multiple networks. For example, a customer might hold:

* USDC on Base
* USDC on Solana

The wallet user must choose the version that matches the network shown on the Bead hosted page. If they pick the wrong one, the send will usually fail or will not settle to the address Bead is monitoring.

For integrators and merchant teams, the key checks are:

* Does the customer have a compatible wallet for the network shown
* Does that wallet show the correct asset on that same network
* Does the customer have enough balance to pay both the invoice amount and network fees

#### Wallet types and what Bead expects

From Bead’s perspective, there are two high level wallet categories.

1. Self custody wallets
   * Examples include Phantom, the Base app (Coinbase’s self custody wallet), and similar applications.
   * The customer controls the private keys.
   * These wallets can usually scan a QR code, choose the correct network, and send directly to the address provided by Bead.
2. Exchange accounts
   * Examples include centralized exchanges where balances are held on the customer’s behalf.
   * These often cannot scan an external QR and pay directly in a point of sale or ecommerce style flow in the same way a self custody wallet can.
   * Transfers usually require moving funds from the exchange into a self custody wallet first.

Bead’s crypto payment flows are designed for self custody wallets.

Some exchanges and wallets provide features that make moving funds into self custody easier. For example:

* A self custody wallet can be linked to an exchange account to make it easier to move assets between the two.
* Wallets may offer a deposit from exchange flow that connects to major exchanges and guides the withdrawal into the wallet.

From an integrator’s point of view, these features are convenience layers around the same core concept: the user is ultimately performing an exchange withdrawal into a self custody wallet, which is then used to pay the Bead hosted payment address.

If a merchant’s customers primarily keep assets on an exchange, they may need to:

* Move funds from the exchange into a self custody wallet, using either a direct withdrawal or an exchange linking feature
* Then pay from that wallet using the hosted page

When you see issues such as “customer is using Coinbase but cannot pay,” it is often because they are trying to use an exchange balance instead of a self custody wallet app such as Base or Phantom.

#### How customers typically fund a wallet

Bead does not provide funding or exchange services. In practice, customers usually get funds into a wallet in one of these ways.

1. On ramp inside the wallet app
   * Many wallets let customers buy crypto using cards or bank transfers inside the app.
   * The details vary by region and provider.
2. Transfers or links from an exchange to a wallet
   * The customer buys USDC, BTC, and any fee tokens on an exchange.
   * The customer then moves those assets into a self custody wallet. This can be done by:
     * Manually withdrawing to a wallet address, or
     * Using features that link an exchange account to the wallet and guide the withdrawal flow directly, where supported.
3. Transfers from another wallet
   * The customer already has assets and fee tokens in another wallet and sends to the wallet they want to use for payment.

For integrators, the main points to understand are:

* Bead only sees the funds when they are sent to the address generated for the payment.
* The path the customer used to fund their wallet is out of scope for Bead, but often drives the types of questions support teams will see.

#### Swaps inside wallets

Many wallets support swaps, where the customer trades one asset for another inside the wallet.

Relevant examples include:

* Swapping from a different asset into USDC
* Swapping into fee tokens, for example into SOL on Solana or ETH on Base for fees
* Using swap or bridge flows to move value between networks, where supported

Important things for integrators to know:

* Swaps are wallet features. Fees and capabilities are controlled by the wallet provider or its partners.
* Swaps often require fee tokens as well, so a customer with zero fee token may need to deposit some before they can perform a swap.
* From Bead’s perspective, a swap is just a way for the customer to prepare their balances. The actual payment is still a send to the Bead generated address for the requested asset and network.

In your own help materials you can encourage merchants to:

* Make sure their teams understand that swaps are possible but not required
* Encourage customers to complete any swap before initiating the payment, to avoid the payment window expiring during a swap

#### Common situations integrators should be aware of

These are patterns that will show up in support conversations.

* Wrong network selected in the wallet
  * The wallet has the asset on a different network than the one requested.
  * The payment fails or never arrives.
* Insufficient fee balance
  * The wallet shows an error about network fees even when the main asset balance looks sufficient.
  * The customer may need to acquire a small amount of ETH on Base, SOL on Solana, or additional BTC for on-chain Bitcoin fees.
* Trying to pay directly from an exchange
  * The customer cannot scan the QR or confirm quickly enough using an exchange account.
  * Withdrawals may be delayed or batched, which does not match a real time payment flow, even when the exchange is linked to a self custody wallet.
* Expired payments
  * The customer starts the flow, leaves the screen open, and pays later.
  * By the time they send, the payment window has expired and the address is no longer considered valid for the original invoice.

Each of these cases can be handled in more detail in your FAQs and Troubleshooting section. This page provides the shared vocabulary and concepts.

#### How this page relates to other documentation

* For a list of currently supported wallets and assets, see [Compatible Crypto Wallets](/reference-guide/operational-guides/compatible-crypto-wallets).
* For running a full test transaction end to end, see [Test Crypto Transactions](/payments/test-crypto-transactions).
* For common issues and questions, see [FAQs and Troubleshooting](/faqs-and-troubleshooting) under [Payments ](/faqs-and-troubleshooting/payments-faqs)and [Environment and Testing](/faqs-and-troubleshooting/environment-and-testing).


# Error Codes

Every non-2xx response from the Bead API returns a standard JSON error object. Use the HTTP status code for high-level handling (retry vs. fix request) and the `error` string for granular logic.

## Error Object Shape

```json
{
  "code": 400,
  "error": "invalid_request",
  "message": "Missing required field address",
  "details": [
    { "field": "address", "issue": "required" }
  ]
}
```

| Field     | Type    | Description                                                   |
| --------- | ------- | ------------------------------------------------------------- |
| `code`    | integer | Mirrors the HTTP status                                       |
| `error`   | string  | Machine-readable error key                                    |
| `message` | string  | Human-readable explanation                                    |
| `details` | array   | Optional list of field issues; present on validation failures |

## Standard Error Catalogue

| HTTP | `error` key            | When it happens                                       | Retry?                       |
| ---- | ---------------------- | ----------------------------------------------------- | ---------------------------- |
| 400  | `invalid_request`      | Malformed JSON or missing top-level fields            | ✘                            |
| 400  | `validation_error`     | Field-level problems (see `details`)                  | ✘                            |
| 401  | `invalid_client`       | Bad `client_id` / `client_secret`                     | ✘                            |
| 401  | `invalid_grant`        | Wrong username or password                            | ✘                            |
| 401  | `unauthorized`         | Token missing or expired                              | Obtain new token             |
| 403  | `insufficient_scope`   | Token lacks required scope                            | ✘                            |
| 403  | `forbidden`            | Auth OK but caller not allowed on this resource       | ✘                            |
| 404  | `not_found`            | Resource ID doesn’t exist or not visible              | ✘                            |
| 409  | `conflict`             | Duplicate operation (e.g., same name)                 | ✘                            |
| 422  | `unprocessable_entity` | Business rule violated (e.g., delete in-use location) | ✘                            |
| 429  | `rate_limit_exceeded`  | Quota exhausted; see `X-RateLimit-Reset`              | ✔︎ after back-off            |
| 500  | `internal_error`       | Unexpected server fault                               | ✔︎ with exponential back-off |
| 503  | `service_unavailable`  | Maintenance or downstream outage                      | ✔︎ after `Retry-After`       |

## Validation Error `details`

Each entry in `details` has:

```json
{ "field": "tenderTypes[0]", "issue": "unsupported_value" }
```

| Issue                    | Meaning                           |
| ------------------------ | --------------------------------- |
| `required`               | Field missing                     |
| `invalid_format`         | Wrong data type or regex mismatch |
| `unsupported_value`      | Value outside allowed enum        |
| `too_long` / `too_short` | String length limits              |
| `out_of_range`           | Numeric bounds violated           |

### Retry guidance quick-chart

| Category                | Example                      | Client action                                         |
| ----------------------- | ---------------------------- | ----------------------------------------------------- |
| **Caller fix**          | 400, 401, 403, 404, 409, 422 | Correct request before retrying                       |
| **Back-off then retry** | 429, 500, 503                | Exponential back-off; obey `Retry-After` when present |

Integrations should log both the HTTP status and `error` key to ease troubleshooting and monitoring.


# Environments & Base URLs

## Environments & Base URLs

Use the matrix below to target the correct services for your environment. Values shown are the currently active endpoints.

| Area                             | Test / Non-prod                              | Production                                |
| -------------------------------- | -------------------------------------------- | ----------------------------------------- |
| **Payment Service API base URL** | `https://api.test.devs.beadpay.io`           | Provided during onboarding                |
| **OIDC / Identity base**         | `https://identity.beadpay.io/realms/nonprod` | `https://identity.beadpay.io/realms/prod` |

### Realms

| Environment     | Realm     |
| --------------- | --------- |
| Test / Non-prod | `nonprod` |
| Production      | `prod`    |

Token endpoint pattern used for all realms

```
{OIDC_BASE}/protocol/openid-connect/token
```

Explicit token URLs

* Test\
  `https://identity.beadpay.io/realms/nonprod/protocol/openid-connect/token`
* Production\
  `https://identity.beadpay.io/realms/prod/protocol/openid-connect/token`

### Authentication

#### Resource Owner Password Credentials for terminals

Terminals authenticate with the ROPC flow to obtain an access token using the terminal-issued username and password.

Token URL Test / Non-prod\
`https://identity.beadpay.io/realms/nonprod/protocol/openid-connect/token`

Token URL Production\
`https://identity.beadpay.io/realms/prod/protocol/openid-connect/token`

Form fields

| Field        | Value                      |
| ------------ | -------------------------- |
| `grant_type` | `password`                 |
| `client_id`  | `bead-terminal`            |
| `username`   | `{TERMINAL_ID}@beadpay.io` |
| `password`   | `{TERMINAL_PASSWORD}`      |
| `scope`      | `openid profile email`     |

cURL example Test / Non-prod

```bash
curl -sS -X POST \
  https://identity.beadpay.io/realms/nonprod/protocol/openid-connect/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data 'grant_type=password' \
  --data 'client_id=bead-terminal' \
  --data 'username=YOUR_TERMINAL_ID@beadpay.io' \
  --data 'password=YOUR_TERMINAL_PASSWORD' \
  --data 'scope=openid profile email'
```

cURL example Production

```bash
curl -sS -X POST \
  https://identity.beadpay.io/realms/prod/protocol/openid-connect/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data 'grant_type=password' \
  --data 'client_id=bead-terminal' \
  --data 'username=YOUR_TERMINAL_ID@beadpay.io' \
  --data 'password=YOUR_TERMINAL_PASSWORD' \
  --data 'scope=openid profile email'
```

Partners using server-to-server integrations can use their assigned OIDC client and flow. Coordinate with your Bead contact to enable.

### Using the API

Include these headers on every request

| Header          | Example                 |
| --------------- | ----------------------- |
| `Authorization` | `Bearer {access_token}` |
| `Content-Type`  | `application/json`      |

#### Example Create a crypto payment in Test / Non-prod

```http
POST https://api.test.devs.beadpay.io/payments/crypto
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "terminalId": "YOUR_TERMINAL_ID",
  "merchantId": "YOUR_MERCHANT_ID",
  "requestedAmount": 100.00,
  "reference": "ORDER123",
  "redirectUrl": "https://yourwebsite.com/payment-success"
}
```

The response includes a `trackingId` and a `paymentUrls.url` to redirect the buyer.

#### Polling payment status

Retrieve details by tracking id

```
GET {API_BASE}/Payments/tracking/{trackingId}
```

This endpoint returns the current payment object.

### Quick Reference

| Item                     | Value                                        |
| ------------------------ | -------------------------------------------- |
| **Test Payment API**     | `https://api.test.devs.beadpay.io`           |
| **OIDC Test base**       | `https://identity.beadpay.io/realms/nonprod` |
| **OIDC Production base** | `https://identity.beadpay.io/realms/prod`    |
| **Token path**           | `/protocol/openid-connect/token`             |
| **Common auth client**   | `bead-terminal` for terminals                |


# Idempotency

Idempotency guarantees that repeating the **same** write request (POST, PUT, PATCH) within a short window returns the original response instead of creating a duplicate object. Bead will add this feature soon; the details below describe the planned contract so you can design for it now.

### Status

Idempotency is **not yet enforced** in Sandbox or Production. Expect network retries to create duplicate records until enforcement is announced in the changelog.

### Header format

| Header          | Example value                          | Notes                            |
| --------------- | -------------------------------------- | -------------------------------- |
| Idempotency-Key | `550e8400-e29b-41d4-a716-446655440000` | Case-insensitive UUID v4 string. |

### Evaluation rules (planned)

| Condition                                                     | Server behaviour                                               |
| ------------------------------------------------------------- | -------------------------------------------------------------- |
| Same `Idempotency-Key` and identical request body within 24 h | Return the cached 2xx response (status and body).              |
| Same `Idempotency-Key` but different body                     | Return **409 Conflict** with an error explaining the mismatch. |
| Key older than 24 h                                           | Treat as a brand-new request.                                  |

Keys are stored per endpoint path; using the same key for different paths does not collide.

### Example

```bash
curl -X POST "$BEAD_API/Locations" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
     -H "Content-Type: application/json" \
     -d '{ "merchantId": "mer_123", "address": {...} }'
```

If the client times out and sends the identical call again with the same key, the API responds with the original **201** body.

### Best practices

* Generate a fresh UUID v4 for each logical operation (one per new location, one per new terminal, etc.).
* Store the key client-side until the operation succeeds or fails definitively.
* Do not reuse keys across resource types or different merchants.
* If you receive **409 Conflict**, inspect the error; it means the same key was used with a modified body.
* Maintain client-side retries with exponential back-off even after idempotency is live; the feature only prevents duplication, it does not guarantee success.

### Go-live checklist for integrators

1. Implement the header now in sandbox calls; it will be ignored until launch.
2. Log any **409 Conflict** responses so you can catch accidental key reuse.
3. Watch the global changelog for the announcement that enforcement is active.


# Rate Limits & Throttling

Bead enforces per-token rate limits on **terminal credentials** (`client_id = bead-terminal`). Every Hosted Payment Page (terminal) has its own quota. If you exceed that quota, the gateway returns **HTTP 429 Too Many Requests**.

### Default limits

| Environment    | Write calls(POST / PUT / PATCH / DELETE) | Read calls(GET)                          |
| -------------- | ---------------------------------------- | ---------------------------------------- |
| **Sandbox**    | 100 requests / minute / terminal token   | 1 000 requests / minute / terminal token |
| **Production** | 60 requests / minute / terminal token    | 600 requests / minute / terminal token   |

Entity-management APIs that use the `bead-integrator` client are low-volume and not metered by this limit; they may be capped separately in future.

### Rate-limit headers

| Header                  | Meaning                                               |
| ----------------------- | ----------------------------------------------------- |
| `X-RateLimit-Limit`     | Maximum calls allowed in the current 60-second window |
| `X-RateLimit-Remaining` | Calls still available after this request              |
| `X-RateLimit-Reset`     | Unix epoch time when the window resets                |

Example

```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 12
X-RateLimit-Reset: 1717521120
```

### 429 response body

```json
{
  "code": 429,
  "error": "rate_limit_exceeded",
  "message": "Quota exhausted. Retry after 2 seconds."
}
```

If a `Retry-After` header is present, wait at least that many seconds before retrying.

### Best practices

* Monitor `X-RateLimit-Remaining` and slow down proactively when it falls below 10 % of the window.
* On 429, use exponential back-off: 1 s → 2 s → 4 s, then resume after `X-RateLimit-Reset`.
* Batch high-volume reads to stay within the GET quota; avoid bursts of parallel writes.
* Sandbox quotas are higher—test under production numbers before go-live.
* Need more headroom? Open a Support ticket with expected TPS and terminal count.

### FAQ

**Q • Are limits enforced per IP or per token?**\
A • Per terminal token. Calls from multiple IPs using the same token share its quota.

**Q • Do retries after a server error count?**\
A • Yes. Each request that reaches the gateway consumes one unit of quota.

**Q • Do webhook deliveries count against my limits?**\
A • No. Webhooks are outgoing from Bead and are not rate-limited.


# Advanced Partner Hierarchy

Most integrations do not need to manage the full partner hierarchy directly. Use this reference when Bead instructs you to create or manage ISO, Agent, Corporate Entity, or Master Merchant partner records.

For most partners, the common operating model is:

```
Partner → Merchant → Location → Terminal → Payment
```

Some larger ISO, super-ISO, reseller, or boarding-platform programs require additional partner records before merchants are created. In those cases, merchants ultimately roll up under a Master Merchant partner record.

### Key rule

Every partner hierarchy branch must resolve to a Master Merchant record.

When a partner is created at the ISO, Agent, or Corporate Entity level, Bead automatically creates the downstream sub-partner records required to reach a Master Merchant.

| Partner record created | Downstream records automatically created by Bead     |
| ---------------------- | ---------------------------------------------------- |
| ISO                    | Agent, Corporate Entity, Master Merchant             |
| Agent                  | Corporate Entity, Master Merchant                    |
| Corporate Entity       | Master Merchant                                      |
| Master Merchant        | No additional partner hierarchy records are required |

For example, when an ISO partner is added, Bead creates the ISO and automatically creates an Agent, Corporate Entity, and Master Merchant under it. You do not need to separately add each downstream level.

### Partner hierarchy

```
ISO
 └─ Agent
    └─ Corporate Entity
       └─ Master Merchant
          └─ Merchant
             └─ Location
                └─ Terminal
                   └─ Payment
```

### Partner types

| Partner type             | API value         | Typical use                                                     |
| ------------------------ | ----------------- | --------------------------------------------------------------- |
| ISO                      | `iso`             | Top-level ISO or super-ISO organization.                        |
| Agent                    | `agent`           | Regional office, sales group, or sub-organization under an ISO. |
| Corporate Entity / Corp  | `corporateEntity` | Business or legal entity layer under an Agent.                  |
| Master Merchant / Master | `masterMerchant`  | Required partner-level record that merchants are created under. |

### Multiple Agents under an ISO

Large ISO or super-ISO relationships may have multiple Agent records under the same ISO. Agent records often represent regional offices, business units, or similar operational groupings.

Example:

```
ISO: Acme Payments
 ├─ Agent: Acme Payments
 ├─ Agent: Acme Payments Agent Office 1
 └─ Agent: Acme Payments Agent Office 2
```

Each Agent branch resolves to its own Corporate Entity and Master Merchant structure so merchants can be boarded under the appropriate Master Merchant.

### Merchant ownership

Merchants are created under the Master Merchant record.

The Master Merchant is the partner record that owns the merchant portfolio for that hierarchy branch. Even when the visible commercial relationship starts at the ISO, Agent, or Corporate Entity level, the hierarchy must resolve to a Master Merchant before merchants can be boarded underneath it.

### When to use this model

Use the advanced partner hierarchy only when Bead has confirmed that the partner relationship requires one or more of the following:

| Scenario                                       | Example                                                                      |
| ---------------------------------------------- | ---------------------------------------------------------------------------- |
| Super-ISO or ISO structure                     | A top-level ISO manages multiple offices or sub-organizations.               |
| Regional Agent organization                    | Each Agent represents a regional office or sales group.                      |
| Boarding platform on behalf of another partner | A software provider boards merchants for a super-ISO or reseller.            |
| Separate merchant portfolios                   | Merchants need to roll up under different Agent or Master Merchant branches. |

### When not to use this model

Do not use the advanced hierarchy for standard merchant onboarding unless Bead has instructed you to do so.

Most partners should continue using the standard merchant onboarding and entity management flow. Adding ISO, Agent, Corporate Entity, or Master Merchant records when they are not needed can make reporting, permissions, and merchant ownership harder to manage.

### Best practice

Create the highest-level partner record Bead has instructed you to create, then use the automatically created downstream records.

Do not manually recreate the downstream hierarchy unless Bead has specifically instructed you to do so. Before adding new partner branches, confirm the intended parent partner ID and target hierarchy with Bead.


# Enumerations & Schemas

### Purpose

Enumerations & Schemas is the canonical reference for every reusable value set and shared object used across Bead APIs. Use this section to validate request bodies, parse responses, and keep client libraries in sync with the platform.

### What you’ll find here

* **Common Field Types** – Reusable objects such as addresses and contacts that appear in multiple APIs.
* **Merchant Onboarding Schema** – Complete schema for `POST /merchant-onboarding/application`, including business identity, contacts, owners, banking, and fee configuration.
* **Settlement Currencies** – Supported currencies for settlement.
* **Tender Types** – Enumerations for supported payment methods. Subpages cover:
  * **Crypto** – Supported digital assets for crypto payments.
  * **Alternative Payments** – Supported peer-to-peer and wallet-based payment methods.

### How to use these pages

* **Validate input**: Reject values not present in the corresponding enum list.
* **Display names and symbols**: Map the code or tender string to the human-readable name provided here.
* **Plan for change**: New rows will appear when Bead adds a rail or asset; unknown values should raise a clear error in your integration.

### Versioning

Enumerations can grow but never break existing values. Schemas may add optional fields; required fields change only in a new API version. Always check the changelog before each release cycle.


# Common Field Types

## Common Field Types

Shared objects and field conventions used across the API. Use these definitions when building requests and interpreting responses.

### Address object

| Field         | Type   | Required | Notes                             |
| ------------- | ------ | -------- | --------------------------------- |
| `address`     | string | Yes      | Street address, max length 512    |
| `address2`    | string | No       | Suite or unit, max length 512     |
| `city`        | string | Yes      | City or locality, max length 128  |
| `state`       | string | Yes      | State or province, max length 128 |
| `countryCode` | string | Yes      | ISO alpha-2, e.g. `US`            |
| `postalCode`  | string | Yes      | ZIP or postal code, max length 32 |

**Example**

```json
{
  "address": "123 Main St",
  "address2": "Suite 200",
  "city": "Springfield",
  "state": "MA",
  "countryCode": "US",
  "postalCode": "01109"
}
```

### Customer object

The `customer` object (`CustomerRequest`) is used on:

* `POST /Payments/crypto`
* `POST /PaymentPages/{id}/request-quote`
* `POST /PaymentPages/{id}/pending-transaction`

It's optional by default on both physical and virtual terminals, but becomes required when a specific tender requires it (for example, Klarna requires `customer` on both physical and virtual terminals) or when merchant configuration, risk review, or compliance flow requires it. See [Create Payment](/payments/create-payment) for terminal-type guidance.

**No partial objects.** If you include `customer`, all required fields below must be present. A partial `customer` object returns `400 Bad Request`. If your flow doesn't need customer details, omit `customer` entirely rather than sending it partially filled.

| Field         | Type             | Required | Notes                        |
| ------------- | ---------------- | -------- | ---------------------------- |
| `firstName`   | string           | Yes      | Max length 128               |
| `lastName`    | string           | Yes      | Max length 128               |
| `email`       | string           | Yes      | Email format, max length 128 |
| `address`     | string           | Yes      | Max length 512               |
| `city`        | string           | Yes      | Max length 128               |
| `state`       | string           | Yes      | Max length 128               |
| `postalCode`  | string           | Yes      | Max length 32                |
| `countryCode` | string           | Yes      | ISO alpha-2, e.g. `US`       |
| `address2`    | string, nullable | No       | Max length 512               |
| `phone`       | string, nullable | No       | Max length 32                |

**Example**

```json
{
  "customer": {
    "firstName": "Jordan",
    "lastName": "Reed",
    "email": "jordan.reed@example.com",
    "address": "456 Market St",
    "address2": "Suite 210",
    "city": "Chicago",
    "state": "IL",
    "postalCode": "60601",
    "countryCode": "US"
  }
}
```

### Contact fields

| Field          | Type           | Required            | Notes                                                       |
| -------------- | -------------- | ------------------- | ----------------------------------------------------------- |
| `contactPhone` | string         | Yes when documented | Local format or E.164 accepted. Prefer E.164 for production |
| `contactEmail` | string         | Yes when documented | Valid email address for notifications or support            |
| `website`      | string or null | Conditional         | Required when business type is virtual. Must be HTTPS       |

### Identifiers

Treat all identifiers as opaque strings. Preserve case and do not parse by prefix.

| Entity   | Example format  |
| -------- | --------------- |
| Partner  | `par_9f2a1cde`  |
| Merchant | `mer_4e5a13aa`  |
| Location | `loc_bfdc6a7f`  |
| Terminal | `term_12345678` |
| Payment  | `pay_abcdef12`  |

### Monetary amounts and volumes

Numeric values are JSON numbers. Do not quote numeric fields.

| Field                  | Type           | Meaning                                   |
| ---------------------- | -------------- | ----------------------------------------- |
| `averageTicketSize`    | number         | Typical transaction amount for the entity |
| `maxTicketSize`        | number         | Maximum expected transaction amount       |
| `highestMonthlyVolume` | number or null | Peak expected monthly volume if available |
| `grossAnnualRevenue`   | number         | Estimated annual processing volume        |

Precision is two decimal places unless a field specifies otherwise. Currency is implied by the merchant or settlement rail unless a currency code is explicitly documented on that endpoint.

### Merchant category codes

| Field                            | Type           | Required | Notes                          |
| -------------------------------- | -------------- | -------- | ------------------------------ |
| `merchantCategoryCode`           | string         | Yes      | Four digit MCC such as 5812    |
| `additionalMerchantCategoryCode` | string or null | No       | Additional MCC when applicable |

Provide MCC values as strings to preserve leading zeros where used.

### Tender types

Use the exact strings shown on the Tender Types pages. Values are case sensitive.

| Example values | Notes                   |
| -------------- | ----------------------- |
| `usdcBase`     | USDC on Base network    |
| `ethereum`     | ETH on Ethereum network |

When a location or terminal omits `tenderTypes`, defaults from the parent are applied according to the entity documentation.

### Timestamps

| Topic    | Convention                                         |
| -------- | -------------------------------------------------- |
| Format   | ISO 8601 in UTC with Z suffix                      |
| Examples | `2025-09-02T16:22:11Z`, `2025-09-02T16:22:11.015Z` |
| Fields   | `created` and `updated` use UTC consistently       |

### Pagination parameters and envelope

Paginated endpoints accept common parameters and return a standard envelope.

<table><thead><tr><th width="140.5">Name</th><th width="124.5">Type</th><th width="95">Required</th><th>Notes</th></tr></thead><tbody><tr><td><code>page</code></td><td>integer</td><td>No</td><td>One based page number with a default of one</td></tr><tr><td><code>pageSize</code></td><td>integer</td><td>No</td><td>Items per page with a sensible default per endpoint</td></tr></tbody></table>

**Response envelope**

<table><thead><tr><th width="141">Field</th><th width="169.5">Type</th><th>Notes</th></tr></thead><tbody><tr><td><code>data</code></td><td>array</td><td>Results for the requested page</td></tr><tr><td><code>total</code></td><td>integer</td><td>Total number of records across all pages</td></tr><tr><td><code>page</code></td><td>integer</td><td>Page number that was returned</td></tr></tbody></table>

Some list endpoints return a bare array when the full set is expected to be small. Those endpoints do not use pagination parameters or the envelope.

### Error object

All error responses follow a consistent JSON shape. Surface `message` to users and log `traceId` for support.

```json
{
  "code": "validation_error",
  "error": "Invalid request",
  "message": "One or more fields are invalid.",
  "traceId": "00-73fffe343fd1d5b150beddb00730bb24-f182c31fd104b782-00",
  "details": [
    { "field": "address.postalCode", "message": "Invalid format" }
  ]
}
```

<table><thead><tr><th width="167.5">Field</th><th width="183.5">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>code</code></td><td>string</td><td>Stable programmatic code such as <code>validation_error</code>, <code>not_found</code>, <code>forbidden</code></td></tr><tr><td><code>error</code></td><td>string</td><td>Short category label used in logs</td></tr><tr><td><code>message</code></td><td>string</td><td>Human readable explanation</td></tr><tr><td><code>traceId</code></td><td>string</td><td>Correlation identifier for support</td></tr><tr><td><code>details</code></td><td>array</td><td>Optional per field errors with <code>field</code> and <code>message</code></td></tr></tbody></table>

### Header conventions

Most Bead APIs use an API key sent in the `X-Api-Key` header. Some legacy or specialized endpoint families use OAuth 2.0 Bearer tokens. Use the authentication method documented for the specific product area you are integrating with.

<table><thead><tr><th width="165.5">Header</th><th width="240.5">Value</th><th>When</th></tr></thead><tbody><tr><td><code>X-Api-Key</code></td><td><code>{apiKey}</code></td><td>Payments, Onboarding, and most other API families</td></tr><tr><td><code>Authorization</code></td><td><code>Bearer &#x3C;access_token></code></td><td>Endpoint families that explicitly require OAuth</td></tr><tr><td><code>Content-Type</code></td><td><code>application/json</code></td><td>Requests with a JSON body</td></tr><tr><td><code>Accept</code></td><td><code>application/json</code></td><td>All requests</td></tr></tbody></table>

### URL and webhook fields

<table><thead><tr><th width="176">Field</th><th>Requirements</th></tr></thead><tbody><tr><td><code>url</code></td><td>Used for callback and webhook style fields such as <code>PUT /Terminals/{terminalId}/webhook</code>. Must be a fully qualified https URL that is routable from Bead. Use a stable hostname (no localhost in production) and keep the path stable so you can rotate secrets without changing the URL.</td></tr><tr><td><code>webhookUrls</code></td><td>Array of URL strings used for per payment overrides on <code>POST /payments/crypto</code>. Each entry must meet the same requirements as <code>url</code>. Be prepared to receive the same event at both the terminal level URL and every URL listed in <code>webhookUrls</code>.</td></tr></tbody></table>


# Merchant Onboarding Schema

Use this page as the schema reference for merchant onboarding create requests.

The current onboarding API supports two create flows:

* **Full application** using `POST /merchant-onboarding/applications`
* **Minimal application** using `POST /merchant-onboarding/applications-short`

#### Full application

Use the full application flow when your system already has most merchant onboarding data and you want to prefill the application before sending it to the signer.

**Endpoint**

```http
POST /merchant-onboarding/applications
```

**Request body**

The full application endpoint uses `InitiateOnboardingRequest`, which requires:

* `merchantData`
* `submitImmediately` *(optional — omit or set to `true` for immediate send; set to `false` to create in Draft status)*

**`merchantData`**

The `merchantData` object uses `MerchantOnboardingRequest`. It includes merchant identity, business details, ownership, banking, fees, and crypto environment settings. `cryptoEnvironment` is required in the full application flow.

**Merchant identity and business details**

```json
{
  "merchantName": "string",
  "partnerId": "string",
  "partnerExternalId": "string",
  "registeredName": "string",
  "dbaName": "string | null",
  "isSettlementOnly": false,
  "isCommissionMerchant": false,
  "commissionMerchantId": "string | null",
  "representsPartnerId": "string | null",
  "proofOfBusinessType": "businessLicense | articlesOfIncorporation | null",
  "registrationDate": "date-time | null",
  "legalEntityType": "soleProprietorship | llc | llp | corporationSType | corporationCType | partnership | nonProfit | government | trust | cooperative | other | null",
  "otherLegalEntity": "string | null",
  "federalTaxIdType": "ein | ssn | itin | null",
  "federalTaxId": "string",
  "naicsCode": "string",
  "merchantCategoryCode": "string",
  "additionalMerchantCategoryCode": "string | null",
  "businessDescription": "string",
  "isSeasonal": true,
  "operationalMonths": ["january", "february"],
  "grossAnnualRevenue": 0,
  "highestMonthlyVolume": 0,
  "averageTicketSize": 0,
  "maxTicketSize": 0
}
```

**Address and location details**

```json
{
  "registeredLegalAddress": {
    "address1": "string",
    "address2": "string | null",
    "city": "string",
    "region": "string",
    "country": "string",
    "postalCode": "string"
  },
  "proofOfAddressType": "utilityBill | bankStatement | other | null",
  "sameAsRegisteredAddress": true,
  "businessMailingAddress": {
    "address1": "string",
    "address2": "string | null",
    "city": "string",
    "region": "string",
    "country": "string",
    "postalCode": "string"
  },
  "isLegalAddress": true,
  "isOperatingAddress": false,
  "isVirtualAddress": false,
  "locationBusinessType": "physical | virtual | null",
  "externalLocationId": "string | null",
  "locationName": "string"
}
```

> **Note:** `isLegalAddress`, `isOperatingAddress`, and `isVirtualAddress` are mutually exclusive. Exactly one of these three flags must be `true`. Setting more than one to `true` will be rejected by the validator.

**Business contact details**

```json
{
  "businessWebsite": "string",
  "businessEmail": "string",
  "businessPhone": "string",
  "mainPointOfContact": {
    "firstName": "string",
    "lastName": "string",
    "email": "string",
    "phoneNumber": "string"
  }
}
```

**Ownership and stakeholder details**

{% code expandable="true" %}

```json
{
  "hasNoOwners": false,
  "stakeholders": [
    {
      "firstName": "string",
      "lastName": "string",
      "middleName": "string | null",
      "suffix": "string | null",
      "dateOfBirth": "date-time",
      "citizenship": "string",
      "ownershipPercentage": 0,
      "ownershipType": "direct | indirect | nonOwner",
      "idType": "driverLicense | passport | nationalId",
      "idNumber": "string",
      "countryOfIssuance": "string",
      "taxIdType": "ein | ssn | itin",
      "taxIdNumber": "string",
      "proofOfIdentityType": "driverLicense | passport | nationalId",
      "proofOfAddressType": "utilityBill | bankStatement | other",
      "address": {
        "address1": "string",
        "address2": "string | null",
        "city": "string",
        "region": "string",
        "country": "string",
        "postalCode": "string"
      },
      "email": "string",
      "phoneNumber": "string",
      "jobTitle": "string",
      "isApplicationSigner": true
    }
  ]
}
```

{% endcode %}

> **Note:** `taxIdNumber` must be exactly 9 digits with no dashes or formatting (e.g., `123456789`). Formatted values such as `123-45-6789` will be rejected.
>
> **Note:** Exactly one stakeholder must have `isApplicationSigner` set to `true`. This designates the person who will receive and sign the hosted onboarding package.

**Banking details**

```json
{
  "bankName": "string",
  "routingNumber": "string",
  "accountNumber": "string",
  "accountType": "checking | savings | null",
  "bankAddress": {
    "address1": "string",
    "address2": "string | null",
    "city": "string",
    "region": "string",
    "country": "string",
    "postalCode": "string"
  },
  "proofOfBankAccountType": "colorizedVoidCheck | bankLetter | null",
  "bankDepositMethod": "ach | wire | null"
}
```

**Fees and crypto environment**

{% code expandable="true" %}

```json
{
  "feeInformation": {
    "achSettlementFixedFee": {
      "sellRate": 0,
      "isBilledByPartner": false
    },
    "wireSettlementFixedFee": {
      "sellRate": 0,
      "isBilledByPartner": false
    },
    "settlementReturnFixedFee": {
      "sellRate": 0,
      "isBilledByPartner": false
    },
    "monthlyMaintenanceFee": {
      "sellRate": 0,
      "isBilledByPartner": false
    },
    "bitcoinFee": {
      "transactionRate": {
        "sellRate": 0,
        "isBilledByPartner": false
      },
      "perItemFee": {
        "sellRate": 0,
        "isBilledByPartner": false
      }
    },
    "stablecoinFee": {
      "transactionRate": {
        "sellRate": 0,
        "isBilledByPartner": false
      },
      "perItemFee": {
        "sellRate": 0,
        "isBilledByPartner": false
      }
    },
    "digitalWalletsFees": [
      {
        "tenderType": "string",
        "paymentLocationType": "inStore | online",
        "transactionRate": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      }
    ]
  },
  "cryptoEnvironment": "sandbox"
}
```

{% endcode %}

`feeInformation` is required, and `cryptoEnvironment` is required. Allowed `cryptoEnvironment` values are:

* `production`
* `sandbox`

#### Minimal application

Use the minimal application flow when you want to create the application shell and let the signer complete most of the hosted onboarding flow.

**Endpoint**

```http
POST /merchant-onboarding/applications-short
```

**Request body**

The minimal application endpoint uses `InitiateEmptyOnboardingRequest`. It requires:

* `merchantName`
* `partnerId`
* `partnerMid`
* `signerFirstName`
* `signerLastName`
* `signerEmail`
* `feeInformation`
* `cryptoEnvironment`

**Minimal schema**

{% code expandable="true" %}

```json
{
  "merchantName": "string",
  "partnerId": "string",
  "partnerMid": "string",
  "signerFirstName": "string",
  "signerLastName": "string",
  "signerEmail": "string",
  "feeInformation": {
    "achSettlementFixedFee": {
      "sellRate": 0,
      "isBilledByPartner": false
    },
    "wireSettlementFixedFee": {
      "sellRate": 0,
      "isBilledByPartner": false
    },
    "settlementReturnFixedFee": {
      "sellRate": 0,
      "isBilledByPartner": false
    },
    "monthlyMaintenanceFee": {
      "sellRate": 0,
      "isBilledByPartner": false
    },
    "bitcoinFee": {
      "transactionRate": {
        "sellRate": 0,
        "isBilledByPartner": false
      },
      "perItemFee": {
        "sellRate": 0,
        "isBilledByPartner": false
      }
    },
    "stablecoinFee": {
      "transactionRate": {
        "sellRate": 0,
        "isBilledByPartner": false
      },
      "perItemFee": {
        "sellRate": 0,
        "isBilledByPartner": false
      }
    },
    "digitalWalletsFees": [
      {
        "tenderType": "string",
        "paymentLocationType": "inStore | online",
        "transactionRate": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "perItemFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "refundFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        },
        "chargebackFee": {
          "sellRate": 0,
          "isBilledByPartner": false
        }
      }
    ]
  },
  "cryptoEnvironment": "sandbox"
}
```

{% endcode %}

**Sandbox note**

When testing in Sandbox, set:

```json
"cryptoEnvironment": "sandbox"
```

For Production, set:

```json
"cryptoEnvironment": "production"
```

#### Shared sub-schemas

**`MailingAddress`**

```json
{
  "address1": "string",
  "address2": "string | null",
  "city": "string",
  "region": "string",
  "country": "string",
  "postalCode": "string"
}
```

**`ShortOwnerInfo`**

```json
{
  "firstName": "string",
  "lastName": "string",
  "email": "string",
  "phoneNumber": "string"
}
```

**`FixedFee`**

```json
{
  "sellRate": 0,
  "isBilledByPartner": false
}
```

**`PercentageFee`**

```json
{
  "sellRate": 0,
  "isBilledByPartner": false
}
```

#### Common enums

**`cryptoEnvironment`**

* `production`
* `sandbox`

**`proofOfBusinessType`**

* `businessLicense`
* `articlesOfIncorporation`

**`proofOfAddressType`**

* `utilityBill`
* `bankStatement`
* `other`

**`taxIdType`**

* `ein`
* `ssn`
* `itin`

**`legalEntityType`**

* `soleProprietorship`
* `llc`
* `llp`
* `corporationSType`
* `corporationCType`
* `partnership`
* `nonProfit`
* `government`
* `trust`
* `cooperative`
* `other`

**`locationBusinessType`**

* `physical`
* `virtual`

**`ownershipType`**

* `direct`
* `indirect`
* `nonOwner`

**`idType`**

* `driverLicense`
* `passport`
* `nationalId`

**`accountType`**

* `checking`
* `savings`

**`proofOfBankAccountType`**

* `colorizedVoidCheck`
* `bankLetter`

**`bankDepositMethod`**

* `ach`
* `wire`

#### Create response schema

Both create endpoints return `MerchantOnboardingResponse`.

```json
{
  "applicationId": "string",
  "envelopeId": "string",
  "status": "string"
}
```

The current create response schema includes:

* `applicationId`
* `envelopeId`
* `status`

#### Status values

The create response `status` uses the current agreement application status enum. Common values include:

* `unsent`
* `sent`
* `completed`
* `declined`
* `signed`
* `resent`
* `failed`

#### Notes

* Use the full application flow when you want to prefill merchant onboarding data.
* Use the minimal application flow when you want the signer to complete most of the hosted onboarding flow.
* `feeInformation` is required for both flows.
* `cryptoEnvironment` is required for both flows.
* In Sandbox, set `cryptoEnvironment` to `sandbox`.

#### Related pages

* [Submit Application](/onboarding/submit-application)
* [Sample Payload](/onboarding/sample-payload)
* [Get Status](/onboarding/get-status)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)


# Settlement Currencies

Settlement currencies list every currency currently enabled for settlement or payment conversion. Fields shown match the `/currencies` endpoint. Use the Code column when you build payment requests, quotes, and when you interpret settlement and reporting responses.

This table applies to:

* Payments created through the Payments APIs
* Settlement records in the Settlement APIs
* Reporting endpoints that expose currency codes and amounts

### Currency table

| Code             | Name                | Currency type | Tender type        | Decimals | Enabled |
| ---------------- | ------------------- | ------------- | ------------------ | -------- | ------- |
| USD              | US Dollar           | fiat          | ethereum           | 2        | ✓       |
| BTC              | Bitcoin (Lightning) | crypto        | bitcoin\_Lightning | 8        | ✓       |
| USDC             | USDC Ethereum       | crypto        | usdC\_Ethereum     | 6        | ✓       |
| USDC\_NPL        | USDC Polygon        | crypto        | usdC\_Polygon      | 6        | ✓       |
| ETH              | Ethereum            | crypto        | ethereum           | 18       | ✓       |
| BTC              | Bitcoin (on-chain)  | crypto        | bitcoin            | 8        | ✓       |
| CIT\_USD\_ALI    | Alipay USD          | walletApps    | alipay             | 2        | ✓       |
| CIT\_USD\_PPAL   | PayPal USD          | walletApps    | paypal             | 2        | ✓       |
| CUS\_USD\_VENMO  | Venmo USD           | walletApps    | venmo              | 2        | ✓       |
| CUS\_USD\_WECHAT | WeChat Pay USD      | walletApps    | weChatPay          | 2        | ✓       |
| USD\_KLARNA      | Klarna USD          | walletApps    | klarna             | 2        | ✓       |

### Column definitions

Code\
Identifier for the currency. Uses ISO 4217 for fiat or a platform specific symbol for crypto and wallet apps. This value appears in payment requests, settlement records, and reporting responses.

Name\
Human readable name for the currency and where relevant the network or platform. For example, USDC Ethereum or Bitcoin Lightning.

Currency type\
High level category for the currency. Values include `fiat`, `crypto`, and `walletApps`. This indicates whether the currency represents traditional money, a digital asset, or a wallet application balance.

Tender type\
Value used in `tenderTypes` arrays when limiting payment methods or interpreting how a payment will be routed. For crypto and wallet entries this typically maps to a specific network, Lightning configuration, or QR platform identifier.

Decimals\
Number of minor unit decimal places the platform accepts and returns for this currency. For example, USD has 2 decimals and Ethereum has 18.

Enabled\
Indicates whether the currency is currently live for both Sandbox and Production. Currencies marked with a check mark are fully enabled. If a currency is not enabled it may be present in schemas but blocked for use.

### Notes

All crypto rows map to testnets in Sandbox environments. Production uses mainnet equivalents for the same codes.

New currencies follow the same schema and will appear in this table when they are enabled. If you see a currency code in an API response that is not listed here, contact the Bead team before relying on it in production.


# Settlement Status Codes

Settlement status codes describe the lifecycle of settlement records at both the batch level and the payment level. Centralizing these values makes it easier to keep your integration and internal tools consistent.

There are three related groups of status codes:

* Merchant settlement batch status
* Merchant payment settlement status
* Payment settlement status

Use this page as the reference for any field that contains a settlement status value.

### Merchant settlement batch status

Merchant settlement batches use a status based on the `MerchantSettlementStatus` enum. This status describes the lifecycle of a settlement batch for a merchant and typically appears as `statusCode` on `MerchantSettlement`.

Values:

* `created`\
  The settlement batch has been created in the system, but settlement processing has not started yet.
* `pending`\
  The settlement batch has been accepted for processing and is waiting for downstream actions such as provider or bank processing.
* `processing`\
  Settlement processing is in progress. This can include generating provider level settlement instructions or creating funding transfers.
* `completed`\
  The settlement batch has completed successfully. Associated funding transfers have been accepted and no further processing is expected.
* `error`\
  A problem occurred while processing this settlement batch. Review error details or operational logs and work with Bead support if needed.

### Merchant payment settlement status

Merchant payment settlement lines use a status based on the `MerchantPaymentSettlementStatus` enum. This status describes how settlement is progressing for a specific payment line and typically appears as `status` on `MerchantPaymentSettlement`.

Values:

* `created`\
  A merchant payment settlement record has been created for this payment, but processing has not begun.
* `pending`\
  The payment has been associated with a settlement batch, and settlement processing is queued.
* `processing`\
  Settlement processing is underway for this payment, for example while a transfer is being created or provider instructions are being executed.
* `completed`\
  Settlement for this payment is complete. Funds are either available or scheduled to be available according to the associated timestamps.
* `error`\
  An error occurred settling this payment. This may be due to provider issues, transfer failures, or configuration problems.

If you see a status that is not in this list, contact the Bead team before relying on it in production logic.

### Payment settlement status

Payment centric settlement records use a status based on the `SettlementStatus` enum. This status focuses on the settlement lifecycle of a payment from a higher level and is used on objects such as `PaymentSettlement`, including the `status` field and the `status` query parameter for endpoints like `GET /Merchants/{merchantId}/payment-settlements`.

Values:

* `storing`\
  Settlement related information is being stored and prepared. This is an initial state and is usually short lived.
* `pending`\
  Settlement is queued for this payment and is waiting to be processed.
* `settling`\
  Active settlement processing is underway. External providers or bank systems may be involved at this stage.
* `completed`\
  Settlement processing has completed successfully for this payment.
* `cancelled`\
  Settlement for this payment has been cancelled and will not complete. Check your business rules before using this status operationally.
* `failed`\
  Settlement processing has failed. Review error information and work with Bead support if this appears unexpectedly.

### Payment settlement status for payment objects

Some endpoints expose a separate `PaymentSettlementStatus` enum specific to payment objects, for example through `paymentSettlementStatusCode` on payment history responses.

Values:

* `created`
* `pending`
* `processing`
* `completed`
* `error`
* `initiationFailed`

These values align with the lifecycle of settlement actions for an individual payment. `initiationFailed` indicates that the platform could not initiate the settlement transfer for this payment, usually due to configuration or provider issues.

### How to use these statuses in integrations

A few recommended patterns when working with settlement statuses:

* Merchant and partner portals
  * Display human friendly labels such as “Pending settlement”, “In progress”, “Completed”, and “Failed” that map directly to the enums.
  * On detail views, show both batch level status (from `MerchantSettlement`) and payment level status (from `MerchantPaymentSettlement` or `PaymentSettlement`).
* Reconciliation jobs
  * Filter for `completed` batches and payment settlements when generating accounting outputs or reports that should not change after the fact.
  * Maintain separate reports or dashboards for `pending`, `processing`, `settling`, or `storing` records that may still change.
* Exception handling
  * Monitor for `error`, `failed`, and `initiationFailed` statuses across both merchant settlement batches and payment settlement lines.
  * Use these to drive alerts or internal queues for manual review and remediation.

Whenever you see a settlement status field in an object or endpoint, refer back to this page to confirm the allowed values and their meaning.


# Tender Types

Tender types identify the payment methods that can be enabled for a partner, merchant, location, terminal, or individual payment.

Use tender types when you need to:

* configure which payment methods are available
* limit a payment to one or more specific tenders
* understand which tender a customer used
* build reporting, filtering, or fulfillment logic by tender
* design customer experiences that match payment timing

Tender type values are API values. They are case-sensitive and must be passed exactly as documented.

### Tender type basics

A tender type represents a payment rail or payment method. Examples include Bitcoin on-chain, Bitcoin Lightning, USDC networks, PayPal, Venmo, Klarna, Alipay, WeChat Pay, and Cash App.

Tender types can appear in different parts of the integration:

| Context                      | How tender types are used                                               |
| ---------------------------- | ----------------------------------------------------------------------- |
| Partner configuration        | Defines which tenders may be available under the partner.               |
| Merchant configuration       | Defines which tenders the merchant may accept.                          |
| Location configuration       | Defines which tenders are available for a physical or virtual location. |
| Terminal configuration       | Defines which tenders are available at a terminal.                      |
| Payment creation             | Optionally limits a specific payment to selected tenders.               |
| Payment status and reporting | Identifies the tender actually used for a payment.                      |

Passing a tender type at payment creation does not enroll a merchant into that tender. The tender must already be enabled for the relevant partner, merchant, location, and terminal context.

### Public tender type values

This page lists the tender types integrators should use when configuring or limiting customer-facing payment experiences.

Some tender type values may exist in the API for internal, legacy, or processor-specific use. Those values are not listed here unless they should be used in public integrations.

| API value           | Display label                     | Category                         | Notes                                                                     |
| ------------------- | --------------------------------- | -------------------------------- | ------------------------------------------------------------------------- |
| `bitcoin`           | BTC (on-chain) / Bitcoin on-chain | Crypto                           | Bitcoin on-chain payment. Can take several minutes or longer to complete. |
| `bitcoin_Lightning` | Bitcoin Lightning                 | Crypto                           | BTC over Lightning. Separate from BTC (on-chain) and generally faster.    |
| `ethereum`          | Ethereum                          | Crypto                           | Ethereum-based crypto tender.                                             |
| `usdC_Polygon`      | USDC on Polygon                   | Crypto                           | USDC tender on Polygon.                                                   |
| `usdC_Ethereum`     | USDC on Ethereum                  | Crypto                           | USDC tender on Ethereum.                                                  |
| `usdcBase`          | USDC on Base                      | Crypto                           | USDC tender on Base.                                                      |
| `usdcSolana`        | USDC on Solana                    | Crypto                           | USDC tender on Solana.                                                    |
| `paypal`            | PayPal                            | Wallet app / alternative payment | PayPal tender.                                                            |
| `venmo`             | Venmo                             | Wallet app / alternative payment | Venmo tender.                                                             |
| `cashApp`           | Cash App                          | Wallet app / alternative payment | Cash App tender.                                                          |
| `alipay`            | Alipay                            | Wallet app / alternative payment | Alipay tender.                                                            |
| `weChatPay`         | WeChat Pay                        | Wallet app / alternative payment | WeChat Pay tender.                                                        |
| `klarna`            | Klarna                            | Buy now, pay later               | Klarna tender.                                                            |

### API values versus display labels

Tender type API values are not always the same as the customer-facing or merchant-facing labels.

For example:

| Display label                     | API value           |
| --------------------------------- | ------------------- |
| BTC (on-chain) / Bitcoin on-chain | `bitcoin`           |
| Bitcoin Lightning                 | `bitcoin_Lightning` |
| USDC on Base                      | `usdcBase`          |
| USDC on Solana                    | `usdcSolana`        |
| WeChat Pay                        | `weChatPay`         |
| Cash App                          | `cashApp`           |

Do not send display labels in API requests.

Do not send:

```json
{
  "tenderTypes": ["BTC (on-chain)"]
}
```

Use:

```json
{
  "tenderTypes": ["bitcoin"]
}
```

### BTC (on-chain) naming

BTC (on-chain) is the customer- and merchant-friendly label for **Bitcoin on-chain** payments.

In the API, BTC (on-chain) is represented by:

```
bitcoin
```

Do not send `BTC (on-chain)`, `btcClassic`, `bitcoinClassic`, or any other label as the tender type.

Use:

```json
{
  "tenderTypes": ["bitcoin"]
}
```

Do not confuse BTC (on-chain) with Bitcoin Lightning.

| Customer-facing name              | API tender type     | Meaning                           |
| --------------------------------- | ------------------- | --------------------------------- |
| BTC (on-chain) / Bitcoin on-chain | `bitcoin`           | Standard Bitcoin network payment. |
| Bitcoin Lightning                 | `bitcoin_Lightning` | Lightning rail for BTC payments.  |

### Bitcoin versus Bitcoin Lightning

`bitcoin` and `bitcoin_Lightning` both involve BTC, but they should not be treated as the same tender experience.

| Tender type         | Customer experience                                                                                                    | Integrator guidance                                                                                          |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `bitcoin`           | Bitcoin on-chain transfer. May require network confirmation and can remain `processing` for several minutes or longer. | Design for asynchronous completion. Fulfill only on `completed`.                                             |
| `bitcoin_Lightning` | Lightning payment flow. Typically faster and more suitable for customer-facing waiting flows.                          | Usually reasonable to keep the customer in the payment flow until final result. Fulfill only on `completed`. |

Use `bitcoin` when the merchant wants to accept BTC (on-chain).

Use `bitcoin_Lightning` when the merchant wants to accept BTC over Lightning.

### BTC (on-chain) timing guidance

BTC (on-chain) should be treated as a longer-running tender.

When a customer pays with BTC (on-chain), the payment may move to `processing` after Bead detects the transaction. That means the transaction is underway, but the payment is not final.

For BTC (on-chain):

* `processing` means payment detected, not completed
* `completed` is the fulfillment trigger
* the customer should not always be expected to keep the payment page open until final completion
* merchants should not release goods, ship orders, grant digital access, or mark invoices as paid until the payment reaches `completed`
* webhooks or payment status checks should be used to confirm final completion

BTC (on-chain) is often a good fit for:

* ecommerce orders with later shipment
* invoices
* service payments before pickup
* deposits
* high-ticket purchases
* high-touch retail environments

BTC (on-chain) is usually not a good fit for:

* fast in-person checkout
* low-ticket high-throughput retail
* instant digital downloads
* immediate account access
* any flow where fulfillment must happen immediately

### Tender selection at payment creation

When creating a payment, use the optional `tenderTypes` array to limit the hosted payment experience to one or more tenders.

#### Example: limit payment to BTC (on-chain)

```json
{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 2500.00,
  "reference": "ORDER-4821",
  "description": "Deposit payment",
  "tenderTypes": ["bitcoin"],
  "webhookUrls": [
    "https://example.com/webhooks/bead/payment"
  ]
}
```

Use this for Bitcoin on-chain / BTC (on-chain) payments.

Because BTC (on-chain) may take longer to complete, make sure the integration can:

* show the customer that payment has been detected
* keep the order or invoice pending while status is `processing`
* listen for webhooks or poll payment status
* fulfill only after `completed`

#### Example: offer BTC (on-chain) and Bitcoin Lightning

```json
{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 100.00,
  "reference": "ORDER-9127",
  "description": "Customer payment",
  "tenderTypes": ["bitcoin", "bitcoin_Lightning"],
  "webhookUrls": [
    "https://example.com/webhooks/bead/payment"
  ]
}
```

Use this when the merchant supports both BTC rails and wants the customer to choose.

#### Example: limit payment to USDC on Base

```json
{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 25.00,
  "reference": "ORDER-2201",
  "description": "Online order",
  "tenderTypes": ["usdcBase"],
  "webhookUrls": [
    "https://example.com/webhooks/bead/payment"
  ]
}
```

Use the exact tender value for the desired USDC network.

#### Example: offer wallet app tenders

```json
{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 75.00,
  "reference": "ORDER-3891",
  "description": "Retail payment",
  "tenderTypes": ["paypal", "venmo", "cashApp"],
  "webhookUrls": [
    "https://example.com/webhooks/bead/payment"
  ]
}
```

Use this when the merchant wants to offer selected wallet app or alternative payment tenders.

### Tender type returned on payments

A payment may be created with one or more allowed tender types, but the customer may choose one tender during the hosted payment experience.

When reading payment status, reporting, or webhook payloads, use the returned tender type to understand which tender was actually used.

For example:

| Returned tender type | Meaning                                          |
| -------------------- | ------------------------------------------------ |
| `bitcoin`            | Customer used BTC (on-chain) / Bitcoin on-chain. |
| `bitcoin_Lightning`  | Customer used Bitcoin Lightning.                 |
| `usdcBase`           | Customer used USDC on Base.                      |
| `usdcSolana`         | Customer used USDC on Solana.                    |
| `paypal`             | Customer used PayPal.                            |
| `venmo`              | Customer used Venmo.                             |
| `cashApp`            | Customer used Cash App.                          |

Use the returned tender type together with `statusCode` to decide fulfillment behavior.

### Tender-specific status handling

Different tenders can have different customer wait expectations.

| Tender group                               | `processing` guidance                                                                   | Fulfillment guidance            |
| ------------------------------------------ | --------------------------------------------------------------------------------------- | ------------------------------- |
| BTC (on-chain) / `bitcoin`                 | Payment detected and underway, but final completion may take several minutes or longer. | Fulfill only after `completed`. |
| Bitcoin Lightning / `bitcoin_Lightning`    | Usually shorter customer-facing window.                                                 | Fulfill only after `completed`. |
| USDC tenders                               | May briefly process while transfer and confirmation complete.                           | Fulfill only after `completed`. |
| Wallet app and alternative payment tenders | Timing depends on the rail.                                                             | Fulfill only after `completed`. |
| Buy now, pay later tenders                 | Timing depends on the provider flow.                                                    | Fulfill only after `completed`. |

Regardless of tender, do not treat `processing` as final payment success.

### Choosing tenders by payment environment

Tender selection should match the merchant’s business model and customer experience.

| Environment                         | Recommended tender strategy                                                                           |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------- |
| Fast physical checkout              | Prefer near-real-time tenders. Avoid BTC (on-chain) by default.                                       |
| High-touch physical retail          | BTC (on-chain) may be appropriate for high-ticket purchases if staff can explain the wait.            |
| Ecommerce with later shipment       | BTC (on-chain) can be appropriate because fulfillment naturally happens later.                        |
| Digital goods with instant delivery | Avoid BTC (on-chain) unless access can remain pending until completion.                               |
| Invoices                            | BTC (on-chain) is often appropriate because reconciliation can happen asynchronously.                 |
| Service pickup payments             | BTC (on-chain) can work well when the customer pays before pickup.                                    |
| Deposits and reservations           | BTC (on-chain) can be appropriate if the merchant waits for completion before committing fulfillment. |

Use the **Choosing Tender Types by Payment Environment** guide for more detailed recommendations.

### Recommended integrator behavior for BTC (on-chain)

When `tenderType` is `bitcoin`:

1. Treat the payment as BTC (on-chain) / Bitcoin on-chain.
2. Show the customer BTC-specific timing guidance.
3. If the payment reaches `processing`, tell the customer the transaction has been detected.
4. Move the customer to an order, invoice, receipt, or status screen.
5. Keep the order or invoice pending.
6. Use Payment Webhooks or payment status checks to detect final status.
7. Fulfill only after `completed`.

Recommended customer-facing message:

```
Your BTC payment has been detected and is now processing.

Bitcoin on-chain payments can take several minutes to confirm. You do not need to keep this page open. The merchant will confirm when the payment is complete.
```

### Common mistakes

#### Using the display label instead of the API value

Do not pass:

```json
{
  "tenderTypes": ["BTC (on-chain)"]
}
```

Use:

```json
{
  "tenderTypes": ["bitcoin"]
}
```

#### Treating Bitcoin and Bitcoin Lightning as interchangeable

Do not assume `bitcoin` and `bitcoin_Lightning` are the same customer experience.

`bitcoin` is BTC (on-chain) / Bitcoin on-chain.

`bitcoin_Lightning` is Bitcoin Lightning.

#### Fulfilling on `processing`

Do not release goods, grant digital access, ship an order, or mark an invoice as paid when the payment is only `processing`.

This is especially important for BTC (on-chain).

Use `completed` as the successful final payment state.

#### Enabling BTC (on-chain) everywhere by default

BTC (on-chain) can be valuable, but it should be enabled where delayed confirmation makes sense.

Avoid enabling it by default in fast checkout or instant-delivery environments unless the merchant has a clear pending-payment workflow.

#### Assuming enum-valid means enabled

A tender type may be a valid API value, but that does not mean it is enabled for a given merchant, location, terminal, or payment flow.

Before passing a tender in `tenderTypes`, confirm it is enabled in the relevant configuration context.

### Operational guidance

Before enabling a tender, confirm that the merchant can operationally support it.

For each tender, ask:

* Can the customer wait for this tender to complete?
* Can the merchant delay fulfillment if needed?
* Can staff explain the payment state?
* Can the integration track final status asynchronously?
* Can the order, invoice, or account remain pending?
* Are webhooks configured?
* Is fulfillment gated on `completed`?

For BTC (on-chain), all of these questions matter. If the answer is no, use faster tenders instead.

### Related pages

* [Create Payment](/payments/create-payment)
* [Payment Statuses](/payments/payment-statuses)
* [Payment Webhooks](/payments/payment-webhooks)
* [Crypto Wallet Flow and Amounts](/payments/crypto-wallet-flow-and-amounts)
* [Why do BTC (on-chain) payments take longer?](/faqs-and-troubleshooting/payments-faqs/why-do-btc-on-chain-payments-take-longer)
* [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment)


# Crypto

Use these string values in `tenderTypes` at the merchant, location, terminal, or payment-request level. Values are case sensitive. Copy them exactly as shown.

Crypto payments in Bead Sandbox use live blockchain networks. Test payments require real assets and real network fee tokens. Keep test amounts small.

For USDC on Base and USDC on Solana, the minimum Bead payment amount is $1.00 USD. This minimum is separate from live-network fees such as ETH on Base or SOL on Solana.

#### Current crypto tenders

| Tender string       | Asset              | Network           | Direction                          | Availability                            | Minimum payment guidance                                                                        | Sandbox and Production network notes                                                                                                                              |
| ------------------- | ------------------ | ----------------- | ---------------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `usdcBase`          | USDC               | Base mainnet      | Receive and auto liquidate to fiat | Available now                           | Minimum Bead payment amount is $1.00 USD                                                        | Uses live Base mainnet in Sandbox and Production. Testing requires real USDC on Base. Payer wallets also need ETH on Base for network fees.                       |
| `usdcSolana`        | USDC               | Solana mainnet    | Receive and auto liquidate to fiat | Available now                           | Minimum Bead payment amount is $1.00 USD                                                        | Uses live Solana mainnet in Sandbox and Production. Testing requires real USDC on Solana. Payer wallets also need SOL for network fees.                           |
| `bitcoin`           | BTC                | Bitcoin mainnet   | Receive and auto liquidate to fiat | Available when enabled for your program | Use small live-network BTC test amounts and confirm any program-specific limits with Bead       | Uses the live Bitcoin network in Sandbox and Production. Testing requires real BTC.                                                                               |
| `bitcoin_Lightning` | BTC over Lightning | Lightning Network | Receive and auto liquidate to fiat | Available when enabled for your program | Use small live-network Lightning test amounts and confirm any program-specific limits with Bead | Uses the live Lightning network in Sandbox and Production. Testing requires a Lightning-capable wallet funded with real BTC. String uses an underscore by design. |

#### Planned or program-specific additions

| Tender string | Asset | Network  | Direction                          | Notes                                                                                                                                              |
| ------------- | ----- | -------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ethereum`    | ETH   | Ethereum | Receive and auto liquidate to fiat | Do not assume this tender is enabled unless Bead confirms it for your program. Production rollout follows certification and program configuration. |

#### Where to set tender types

| Level           | Field                                       | Behavior                                                                                                                 |
| --------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Merchant        | `tenderTypes` on the merchant object        | Acts as the default for locations created under the merchant                                                             |
| Location        | `tenderTypes` on the location object        | Overrides the merchant defaults for that location when provided                                                          |
| Terminal        | `tenderTypes` on the terminal object        | Overrides the location settings for that terminal when provided                                                          |
| Payment request | `tenderTypes` on the Create Payment request | Narrows the hosted payment experience for a single payment without changing the merchant, location, or terminal defaults |

If a merchant, location, or terminal level omits `tenderTypes`, it inherits from its parent. Passing an empty array clears any override at that level and reverts to the parent defaults.

For a payment request, omit `tenderTypes` to let the hosted payment page present the tenders enabled for the terminal. Send `tenderTypes` when you want a specific payment experience, such as a dedicated USDC on Base checkout button.

#### Example payloads

Create a location that inherits merchant defaults:

```json
{
  "name": "Downtown Flagship",
  "address": {
    "address1": "123 Main St",
    "city": "Springfield",
    "region": "MA",
    "country": "US",
    "postalCode": "01109"
  }
}
```

Create a location that explicitly sets crypto tenders:

```json
{
  "name": "Airport Kiosk",
  "address": {
    "address1": "987 Terminal Rd",
    "city": "Springfield",
    "region": "MA",
    "country": "US",
    "postalCode": "01109"
  },
  "tenderTypes": ["usdcBase", "usdcSolana"]
}
```

Create a terminal that narrows tenders under the same location:

```json
{
  "locationId": "loc_bfdc6a7f",
  "tenderTypes": ["usdcBase"]
}
```

Create a payment that limits the hosted payment experience to USDC on Base:

```json
{
  "terminalId": "{terminalId}",
  "merchantId": "{merchantId}",
  "requestedAmount": 1.00,
  "tenderTypes": ["usdcBase"]
}
```

Create a payment that offers both USDC Base and USDC Solana:

```json
{
  "terminalId": "{terminalId}",
  "merchantId": "{merchantId}",
  "requestedAmount": 1.00,
  "tenderTypes": ["usdcBase", "usdcSolana"]
}
```

#### Minimum payment amounts and fees

Minimum payment amounts can vary by tender.

For USDC on Base and USDC on Solana, the minimum Bead payment amount is $1.00 USD. If a payment request includes only one of these tenders, or if the hosted payment page is expected to offer either of these tenders, use `requestedAmount` of `1.00` or higher.

This minimum is separate from wallet and network fees.

| Tender              |                    Bead minimum | Fee token needed by payer wallet                  |
| ------------------- | ------------------------------: | ------------------------------------------------- |
| `usdcBase`          |                       $1.00 USD | ETH on Base                                       |
| `usdcSolana`        |                       $1.00 USD | SOL on Solana                                     |
| `bitcoin`           | Confirm program-specific limits | BTC                                               |
| `bitcoin_Lightning` | Confirm program-specific limits | BTC through the Lightning wallet and routing path |

Wallet apps, exchanges, and on-ramp providers may have their own minimum purchase amounts or fees. Those limits are separate from the Bead payment minimum.

#### Validation and behavior

| Topic                    | Guidance                                                                                                                                                                                   |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Case sensitivity         | Values are case sensitive. Use exactly the strings shown in the tables.                                                                                                                    |
| Combination rules        | You can set one or many values in the array. The platform validates that each string is recognized.                                                                                        |
| Payment-request behavior | `tenderTypes` on a Create Payment request limits the available tenders for that payment only. It does not change the merchant, location, or terminal configuration.                        |
| Settlement behavior      | Crypto receipts auto liquidate to the sponsor bank FBO account in fiat according to your program rules.                                                                                    |
| Sandbox behavior         | Sandbox crypto payments use live blockchain networks. Test payments require real assets and real network fee tokens. Live-network fees can vary by network congestion and wallet behavior. |
| USDC minimums            | `usdcBase` and `usdcSolana` require a minimum Bead payment amount of $1.00 USD.                                                                                                            |
| Backward compatibility   | New tender strings can be added without a version change. Unknown strings are rejected with a validation error.                                                                            |

#### Deprecations and naming hygiene

| Old value       | Status     | Replacement                                                                                         |
| --------------- | ---------- | --------------------------------------------------------------------------------------------------- |
| `usdC_Ethereum` | Do not use | Use `ethereum` only when ETH becomes available for your program, or use `usdcBase` for USDC on Base |
| `usdC_Polygon`  | Do not use | No current replacement in Production. Follow the current tender tables for supported USDC networks  |

If your codebase contains legacy strings, migrate them to the current values shown above.

Do not convert tender strings to snake case, lowercase, or display labels in API requests. For example, use `usdcBase`, not `usdc_base`; use `bitcoin_Lightning`, not `bitcoin_lightning`.

#### Related topics

| Topic                                                                                                                                 | Where                               |
| ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| [Create Payment](/payments/create-payment)                                                                                            | Payments section                    |
| [Test Crypto Transactions](/payments/test-crypto-transactions)                                                                        | Payments section                    |
| [Crypto payments, environment and testing](/faqs-and-troubleshooting/environment-and-testing/crypto-payments-environment-and-testing) | FAQs & Troubleshooting              |
| [Compatible Crypto Wallets](/reference-guide/operational-guides/compatible-crypto-wallets)                                            | Reference Guide, Operational Guides |
| [Environments and Base URLs](/reference-guide/core-concepts/environments-and-base-urls)                                               | Reference Guide                     |
| [Location Management](/entity-management/location-management)                                                                         | Entity Management section           |
| [Payment Webhooks and Event Reference](/payments/payment-webhooks)                                                                    | Reference Guide                     |
| [Download OpenAPI and Postman](/reference-guide/endpoint-index/download-openapi)                                                      | Reference Guide                     |


# Alternative Payments

Alternative-payment tender types cover wallet apps and QR-based methods such as Alipay and PayPal.\
Use the **Tender type** string in the `tenderTypes` array when you need to allow or block a specific method at the terminal or location level.

| Tender type | Display name | Currency code(s) | Notes                                         |
| ----------- | ------------ | ---------------- | --------------------------------------------- |
| `alipay`    | Alipay       | USD              | Near-instant QR payment for Chinese wallets   |
| `paypal`    | PayPal       | USD              | Customer approves via PayPal app or web       |
| `venmo`     | Venmo        | USD              | Redirect to Venmo app; QR fallback available  |
| `weChatPay` | WeChat Pay   | USD              | Popular China-based wallet; QR scan or in-app |
| `klarna`    | Klarna       | USD              | Buy-now-pay-later flow; supports instalments  |

Currency codes reflect the settlement currency returned by the `/currencies` endpoint.\
All alternative payments share the same settlement flow as card and crypto tenders: the charge is final once the provider callback confirms success.


# Endpoint Index

### Purpose

Endpoint Index is the one-stop directory for every public Bead API path. Use it to locate a call quickly, copy a URL, or jump to the full reference page without scanning multiple sections.

### What’s inside

| Page                                                                               | What you’ll find                                                                                                                     |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| [**Table View**](/reference-guide/endpoint-index/table-view-all-apis)              | A table listing every path, method, and one-line purpose. Ideal for quick look-ups or copy-paste.                                    |
| [**Download OpenAPI / Postman**](/reference-guide/endpoint-index/download-openapi) | Direct links to the live OpenAPI JSON files (sandbox and production) and a note that ready-made Postman collections are coming soon. |

### How to use this section

1. Open **Table View** and find or Ctrl + F to find the endpoint you need.
2. Click the path (or follow the sidebar) to jump to the detailed docs for that call.
3. If you prefer machine-readable specs, grab the OpenAPI URL from [**Download OpenAPI / Postman** ](/reference-guide/endpoint-index/download-openapi)and import it into Swagger, Insomnia, or Postman.

Endpoints are updated automatically whenever the platform releases; check the changelog for additions or breaking changes.


# Table View (All APIs)

This page is a quick lookup for common endpoints and paths across Bead APIs. Use the correct environment base URL for the API you are calling, then append the paths listed below.

Authentication depends on the API family, but current Bead integrations generally use an API key sent in the `X-Api-Key` header.

For current integrations:

* Payments APIs use `X-Api-Key: <apiKey>`
* Onboarding APIs use `X-Api-Key: <apiKey>`
* Management, reporting, and settlement APIs generally use `X-Api-Key: <apiKey>` unless Bead has provided different endpoint-specific guidance
* Some legacy or specialized endpoint families may still use OAuth 2.0 Bearer tokens

See [Authentication](/authentication) for details and examples.

**Base URLs and token URL**

API base URLs:

* Sandbox: `https://api.test.devs.beadpay.io`
* Production: `https://api.devs.beadpay.io`

OAuth token URL format for legacy or OAuth-based APIs:

* `POST https://identity.beadpay.io/realms/{realm}/protocol/openid-connect/token`

Realm examples:

* Sandbox: `nonprod`
* Production: `prod`

**Standard headers**

For JSON APIs:

* `Accept: application/json`

For requests with a JSON body:

* `Content-Type: application/json`

Primary Bead API authentication header:

* `X-Api-Key: <apiKey>`

OAuth authentication header, when specifically required:

* `Authorization: Bearer <access_token>`

**Payments (API key)**

These endpoints use API key authentication.

| Method | Path                              | Summary                                                                                   |
| ------ | --------------------------------- | ----------------------------------------------------------------------------------------- |
| POST   | `/Payments/crypto`                | Create a hosted payment page and receive `trackingId`, `paymentPageId`, and `paymentUrls` |
| GET    | `/Payments/tracking/{trackingId}` | Get payment status by `trackingId`                                                        |

**Onboarding (API key)**

These endpoints use API key authentication.

| Method | Path                                                                           | Summary                                                  |
| ------ | ------------------------------------------------------------------------------ | -------------------------------------------------------- |
| POST   | `/merchant-onboarding/applications`                                            | Initiate merchant onboarding with full merchant data     |
| POST   | `/merchant-onboarding/applications-short`                                      | Initiate merchant onboarding with minimal information    |
| GET    | `/merchant-onboarding/applications/{applicationId}`                            | Retrieve a merchant onboarding application               |
| PUT    | `/merchant-onboarding/applications/{applicationId}`                            | Resubmit a merchant onboarding application               |
| POST   | `/merchant-onboarding/applications/{applicationId}/submit`                     | Submit a Draft application for signature                 |
| GET    | `/merchant-onboarding/applications/{applicationId}/history`                    | Retrieve application history and audit events            |
| POST   | `/merchant-onboarding/applications/{applicationId}/attachments`                | Upload an attachment to a Draft application              |
| GET    | `/merchant-onboarding/applications/{applicationId}/attachments`                | List attachments for an application                      |
| GET    | `/merchant-onboarding/applications/{applicationId}/attachments/{attachmentId}` | Download a single attachment                             |
| DELETE | `/merchant-onboarding/applications/{applicationId}/attachments/{attachmentId}` | Delete an attachment from a Draft application            |
| POST   | `/merchant-onboarding/{applicationId}/cancel`                                  | Cancel the signing process by revoking the signing token |
| POST   | `/merchant-onboarding/{partnerId}/webhook`                                     | Register an onboarding webhook configuration             |
| PUT    | `/merchant-onboarding/{partnerId}/webhook`                                     | Register or update an onboarding webhook configuration   |
| DELETE | `/merchant-onboarding/{partnerId}/webhook`                                     | Remove an onboarding webhook configuration               |

{% hint style="warning" %} **Deprecated:** `GET /merchant-onboarding/applications` is deprecated. Use `GET /Partners/{id}/applications` instead, listed in the Partners and merchants table below. See [List Applications](/onboarding/list-applications). {% endhint %}

Common query parameters for onboarding application lists:

| Name            | Type      | Notes                                                                         |
| --------------- | --------- | ----------------------------------------------------------------------------- |
| `From`          | date-time | Filter by updated date from this timestamp                                    |
| `To`            | date-time | Filter by updated date to this timestamp                                      |
| `Statuses`      | array     | Filter by application status                                                  |
| `Search`        | string    | Free text search, such as merchant name, signer email, or partner external ID |
| `Page`          | integer   | Zero-based page number                                                        |
| `PageSize`      | integer   | Items per page, maximum `100`                                                 |
| `SortBy`        | string    | Field to sort by                                                              |
| `SortDirection` | string    | Sort direction                                                                |

**Locations (API key)**

These endpoints use API key authentication unless Bead has provided different endpoint-specific guidance.

| Method | Path                        | Summary                                 |
| ------ | --------------------------- | --------------------------------------- |
| POST   | `/Merchants/{id}/locations` | Create a new location under a merchant  |
| GET    | `/Merchants/{id}/locations` | List locations for a merchant           |
| GET    | `/Locations/{id}`           | Get a single location                   |
| PUT    | `/Locations/{id}`           | Update a location                       |
| PATCH  | `/Locations/{id}/enabled`   | Enable or disable a location            |
| DELETE | `/Locations/{id}`           | Delete a location that has no terminals |
| GET    | `/Locations/{id}/terminals` | List terminals for a location           |

**Terminals and webhooks (API key)**

These endpoints use API key authentication unless Bead has provided different endpoint-specific guidance.

| Method | Path                        | Summary                                   |
| ------ | --------------------------- | ----------------------------------------- |
| POST   | `/Terminals`                | Create a terminal                         |
| GET    | `/Terminals/{id}`           | Get a single terminal                     |
| PUT    | `/Terminals/{id}`           | Update a terminal                         |
| DELETE | `/Terminals/{id}`           | Delete a terminal                         |
| GET    | `/Merchants/{id}/terminals` | List terminals for a merchant             |
| GET    | `/Locations/{id}/terminals` | List terminals for a location             |
| POST   | `/Terminals/{id}/clone`     | Clone a terminal                          |
| GET    | `/Terminals/{id}/api-keys`  | Retrieve a terminal API key               |
| POST   | `/Terminals/{id}/api-keys`  | Reset a terminal API key                  |
| PUT    | `/Terminals/{id}/webhook`   | Set or update the terminal webhook        |
| DELETE | `/Terminals/{id}/webhook`   | Remove the terminal webhook configuration |

Terminal webhook body:

| Field | Type   | Required | Notes                                       |
| ----- | ------ | -------- | ------------------------------------------- |
| `url` | string | Yes      | HTTPS endpoint that receives payment events |

**Reporting payment history (API key)**

These endpoints use API key authentication unless Bead has provided different endpoint-specific guidance.

| Method | Path                       | Summary                                              |
| ------ | -------------------------- | ---------------------------------------------------- |
| GET    | `/Partners/{id}/payments`  | Partner payment history with pagination and filters  |
| GET    | `/Merchants/{id}/payments` | Merchant payment history with pagination and filters |
| GET    | `/Terminals/{id}/payments` | Terminal payment history with pagination and filters |

Common query parameters for payment history:

| Name                | Type      | Notes                                      |
| ------------------- | --------- | ------------------------------------------ |
| `From`              | date-time | Filter by payment date from this timestamp |
| `To`                | date-time | Filter by payment date to this timestamp   |
| `TrackingId`        | string    | Filter by tracking ID                      |
| `PageId`            | string    | Filter by payment page ID                  |
| `ReqCurrencyId`     | integer   | Filter by requested currency ID            |
| `PayCurrencyId`     | integer   | Filter by payment currency ID              |
| `CustomerId`        | string    | Filter by customer ID                      |
| `DescriptionSearch` | string    | Search payment description                 |
| `PaymentCode`       | string    | Filter by payment code                     |
| `Reference`         | string    | Filter by payment reference                |
| `StatusCode`        | array     | Filter by one or more payment statuses     |
| `TenderType`        | array     | Filter by one or more tender types         |
| `LocationName`      | string    | Filter by location name                    |
| `TerminalName`      | string    | Filter by terminal name                    |
| `Page`              | integer   | Zero-based page number                     |
| `PageSize`          | integer   | Items per page, maximum `100`              |
| `SortBy`            | string    | Field to sort by                           |
| `SortDirection`     | string    | Sort direction                             |

Typical response envelope:

| Field   | Type    | Notes                  |
| ------- | ------- | ---------------------- |
| `data`  | array   | Page of results        |
| `total` | integer | Total matching records |
| `page`  | integer | Current page           |

**Settlement and reconciliation (API key)**

These endpoints use API key authentication unless Bead has provided different endpoint-specific guidance.

Use these APIs when you need settlement records, grouped payment batches, funded deposits, merchant settlement batches, or payment-level settlement detail.

**Settlement records**

| Method | Path                         | Summary                                    |
| ------ | ---------------------------- | ------------------------------------------ |
| GET    | `/Settlements`               | List settlement records with filters       |
| GET    | `/Settlements/{id}`          | Get a single settlement record             |
| GET    | `/Settlements/{id}/batches`  | List batches associated with a settlement  |
| GET    | `/Settlements/{id}/deposits` | List deposits associated with a settlement |

Common query parameters for settlement records:

| Name            | Type    | Notes                                                           |
| --------------- | ------- | --------------------------------------------------------------- |
| `From`          | date    | Filter by settlement date from this date                        |
| `To`            | date    | Filter by settlement date to this date                          |
| `Status`        | string  | Filter by settlement status                                     |
| `Type`          | string  | Filter by settlement type, such as crypto, wallet apps, or fiat |
| `MerchantIds`   | array   | Filter by one or more merchant IDs                              |
| `Page`          | integer | Zero-based page number                                          |
| `PageSize`      | integer | Items per page, maximum `100`                                   |
| `SortBy`        | string  | Field to sort by                                                |
| `SortDirection` | string  | Sort direction                                                  |

**Batches**

| Method | Path                              | Summary                                   |
| ------ | --------------------------------- | ----------------------------------------- |
| GET    | `/Batches`                        | List payment batches with filters         |
| GET    | `/Batches/{id}`                   | Get a single batch                        |
| GET    | `/Batches/merchants/{merchantId}` | List batches for a merchant               |
| GET    | `/Settlements/{id}/batches`       | List batches associated with a settlement |

Common query parameters for batches:

| Name            | Type    | Notes                                |
| --------------- | ------- | ------------------------------------ |
| `From`          | date    | Filter by batch date from this date  |
| `To`            | date    | Filter by batch date to this date    |
| `Status`        | string  | Filter by batch status               |
| `Type`          | string  | Filter by batch type                 |
| `MerchantIds`   | array   | Filter by one or more merchant IDs   |
| `SettlementIds` | array   | Filter by one or more settlement IDs |
| `Page`          | integer | Zero-based page number               |
| `PageSize`      | integer | Items per page, maximum `100`        |
| `SortBy`        | string  | Field to sort by                     |
| `SortDirection` | string  | Sort direction                       |

**Deposits**

| Method | Path                         | Summary                                    |
| ------ | ---------------------------- | ------------------------------------------ |
| GET    | `/Deposits`                  | List deposit records with filters          |
| GET    | `/Deposits/{id}`             | Get a single deposit record                |
| GET    | `/Settlements/{id}/deposits` | List deposits associated with a settlement |

Common query parameters for deposits:

| Name            | Type    | Notes                                         |
| --------------- | ------- | --------------------------------------------- |
| `From`          | date    | Filter by deposit date from this date         |
| `To`            | date    | Filter by deposit date to this date           |
| `Status`        | string  | Filter by deposit status                      |
| `Method`        | string  | Filter by deposit method, such as ACH or wire |
| `MerchantIds`   | array   | Filter by one or more merchant IDs            |
| `SettlementIds` | array   | Filter by one or more settlement IDs          |
| `Page`          | integer | Zero-based page number                        |
| `PageSize`      | integer | Items per page, maximum `100`                 |
| `SortBy`        | string  | Field to sort by                              |
| `SortDirection` | string  | Sort direction                                |

**Merchant settlements**

| Method | Path                                                          | Summary                                                                            |
| ------ | ------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| GET    | `/MerchantSettlements/merchant/{merchantId}`                  | List merchant settlements for a merchant                                           |
| GET    | `/MerchantSettlements/{merchantSettlementId}`                 | Get a single merchant settlement                                                   |
| GET    | `/MerchantSettlements/payment-ids/merchant-settlement`        | List payment IDs associated with a merchant settlement                             |
| GET    | `/MerchantSettlements/settlement-info/{merchantSettlementId}` | Get payment-level detail for a merchant settlement                                 |
| GET    | `/MerchantSettlements/merchant/between`                       | Get payment and merchant payment settlement records for a merchant and time window |
| GET    | `/MerchantSettlements/merchant-payment-settlements/merchant`  | List merchant payment settlement records for a merchant                            |

**Payment settlements**

| Method | Path                                          | Summary                                        |
| ------ | --------------------------------------------- | ---------------------------------------------- |
| GET    | `/Merchants/{merchantId}/payment-settlements` | List payment settlement records for a merchant |

Common query parameters for merchant and payment settlements:

| Name                   | Type      | Notes                                          |
| ---------------------- | --------- | ---------------------------------------------- |
| `merchantId`           | string    | Merchant identifier                            |
| `merchantSettlementId` | string    | Merchant settlement identifier                 |
| `status`               | string    | Filter by settlement status where supported    |
| `startTime`            | date-time | Start of the settlement window where supported |
| `endTime`              | date-time | End of the settlement window where supported   |
| `page`                 | integer   | Zero-based page number                         |
| `pageSize`             | integer   | Items per page                                 |
| `sortByDirection`      | string    | Sort direction where supported                 |

**Partners and merchants (API key)**

These endpoints use API key authentication unless Bead has provided different endpoint-specific guidance.

| Method | Path                                   | Summary                                                                      |
| ------ | -------------------------------------- | ---------------------------------------------------------------------------- |
| GET    | `/Partners/all`                        | List partners with pagination and filters                                    |
| GET    | `/Partners/{id}`                       | Get a single partner                                                         |
| POST   | `/Partners/{parentId}/partners`        | Create a child partner under a parent partner                                |
| GET    | `/Partners/{id}/api-keys`              | List API keys for a partner                                                  |
| POST   | `/Partners/{id}/api-keys`              | Create a partner API key                                                     |
| GET    | `/Partners/{id}/applications`          | List onboarding applications for a partner and its descendants, with filters |
| GET    | `/Merchants/all`                       | List merchants with pagination and filters                                   |
| GET    | `/Merchants/{id}`                      | Get a single merchant                                                        |
| POST   | `/Merchants`                           | Create a merchant                                                            |
| PUT    | `/Merchants/{id}`                      | Update a merchant                                                            |
| DELETE | `/Merchants/{id}`                      | Delete a merchant                                                            |
| GET    | `/Partners/{id}/merchants`             | List merchants for a partner when supported                                  |
| GET    | `/Partners/{id}/representing-merchant` | Get the merchant represented by a partner when supported                     |

See [List Applications](/onboarding/list-applications) for the full parameter and response reference for `GET /Partners/{id}/applications`.

**Error interpretation**

| Status | What it usually means                                            |
| ------ | ---------------------------------------------------------------- |
| 400    | Invalid input or validation failed                               |
| 401    | Missing or invalid credential                                    |
| 403    | Authenticated but not permitted for the resource or relationship |
| 404    | Not found or not visible in the current context                  |
| 409    | Duplicate or state conflict                                      |
| 422    | Business rule failure                                            |
| 500    | Unexpected server error                                          |

API key notes:

* 401 usually means `X-Api-Key` is missing, invalid, sent using the wrong header name, or tied to the wrong environment.
* 403 usually means the API key is valid but not permitted for the merchant, partner, terminal, or resource context.
* Do not send masked API keys or deprecated request body API key fields.

OAuth notes:

* OAuth may still apply to legacy or endpoint-specific use cases.
* 401 usually means the access token is missing or expired.
* 403 usually means the caller does not have permission for the target resource.


# Download OpenAPI

Use this page to retrieve the latest Bead OpenAPI specification in JSON format.

The OpenAPI file is useful for:

* reviewing available endpoints, request bodies, and response models
* generating client libraries
* validating requests and responses
* aligning internal tooling with the current public schema

### What you can download

| Asset      | Purpose                                      | Format |
| ---------- | -------------------------------------------- | ------ |
| OpenAPI v1 | Full REST specification for public endpoints | JSON   |

#### Download

* **OpenAPI v1 (JSON):** <https://api.test.devs.beadpay.io/openapi/v1.json>

### Base URLs referenced by the specification

| Environment | REST API base URL                  |
| ----------- | ---------------------------------- |
| Sandbox     | `https://api.test.devs.beadpay.io` |
| Production  | `https://api.beadpay.io`           |

#### Notes

* Use Sandbox for functional testing and integration QA.
* Use Production for live merchant traffic and settlement.
* Keep Sandbox and Production credentials separate.
* Store environment-specific values in configuration so switching environments is not a code change.

### Authentication notes

Bead supports multiple authentication methods depending on the API family you are calling.

#### Payments

Payments endpoints use terminal API key authentication.

Send:

```http
X-Api-Key: {apiKey}
```

Important:

* `apiKey` is the usable secret credential
* `maskedApiKey` is not usable

#### Onboarding and related administrative APIs

Use the authentication method documented on the specific product-area page. Where onboarding and related admin/entity-management guides show API key authentication, send:

```http
X-Api-Key: {apiKey}
```

Do not assume a single authentication method applies to every Bead API. Always follow the documentation for the endpoint family you are using.

### Working with the OpenAPI file

You can use the OpenAPI specification to:

* inspect available paths and methods
* review required headers and request bodies
* confirm response models and status codes
* generate SDKs or typed clients with your preferred tooling
* import the file into internal API tools that support OpenAPI

### Versioning and updates

| Topic                  | Guidance                                                                                |
| ---------------------- | --------------------------------------------------------------------------------------- |
| Spec version           | OpenAPI v1 is the current public specification                                          |
| Backward compatibility | Breaking changes are announced on the Changelog and communicated to registered partners |
| Refresh cadence        | Re-download the OpenAPI file when you see a relevant update on the Changelog            |

### Troubleshooting

| Symptom                                          | Likely cause                                | Fix                                                                                        |
| ------------------------------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Generated client does not match current behavior | Local spec file is outdated                 | Download the latest OpenAPI file again                                                     |
| 401 on API requests                              | Wrong credential type or invalid credential | Confirm the authentication method for that API family and verify the credential being sent |
| 404 on a known endpoint                          | Wrong base URL or environment               | Confirm you are using the correct Sandbox or Production host                               |
| Validation errors on request bodies              | Request shape does not match schema         | Compare your request body against the OpenAPI model and required fields                    |

### Next steps

| Action                                      | Where                                             |
| ------------------------------------------- | ------------------------------------------------- |
| Review shared conventions and auth guidance | [Reference Guide](/reference-guide)               |
| Explore methods and paths                   | [Endpoint Index](/reference-guide/endpoint-index) |
| Review onboarding workflows                 | [Onboarding](/onboarding)                         |
| Review payment workflows                    | [Payments](/payments)                             |
| See recent API changes                      | [Changelog](/changelog)                           |


# Payment Flows

This section provides an overview of how Bead enables digital payments across in store and online environments, followed by reference examples of common customer payment flows. The material here is intended to help integrators understand end to end behavior and system interactions rather than prescribe user interface or experience design.

#### Payment Flow Overview

Customers increasingly expect payment options beyond traditional credit and debit cards, including digital wallets, buy now pay later options, and cryptocurrency. Bead enables businesses to accept these digital payment methods while settling funds directly in US dollars to the merchant’s bank account.

The video below provides a high level walkthrough of a complete digital payment flow, demonstrating how a business can accept a digital payment without directly handling cryptocurrency. While the example shown illustrates a customer completing payment from a separate device, the underlying flow applies across multiple payment contexts.

End to End Digital Payment Flow (Reference Overview)

{% embed url="<https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjL0BeBYhFGcs8tIiuCcV%2Fuploads%2F44aVYIlA8gQYqS7FiF48%2FBead_Demo_Video_V3_11.27.25.mp4?alt=media&token=46f59f60-8c6b-4325-ba51-2fcefeb3f3a2>" %}

### Choosing the right payment flow

Different tender types create different customer and merchant experiences. Before enabling a tender for a merchant, location, terminal, or payment flow, confirm that the tender fits the environment where it will be used.

For example, a tender that works well for an invoice or ecommerce order may not be appropriate for fast in-person checkout. BTC (on-chain) / Bitcoin on-chain is a good example: it can be valuable for invoices, delayed fulfillment, deposits, and high-ticket transactions, but it may not be appropriate when the customer expects immediate goods or instant digital access.

Use the Choosing Tender Types by Payment Environment guide before enabling BTC (on-chain) or any tender that may require asynchronous completion.

### Flow planning checklist

When designing a payment flow, decide:

* whether the customer is paying in person or online
* whether the customer expects immediate fulfillment
* whether the merchant can delay fulfillment until final payment completion
* whether staff can explain a pending payment state
* whether the customer can leave the payment page before final completion
* whether your system can use webhooks or payment status checks to confirm the final result
* which tenders should be available for this specific merchant, location, terminal, or payment

For most payment flows, fulfillment should be gated on the payment reaching `completed`.

For BTC (on-chain) / Bitcoin on-chain, the payment may reach `processing` after the transaction is detected, but the merchant should still wait for `completed` before releasing goods, shipping an order, granting access, or marking an invoice as paid.

### High Level Transaction Flow

To complete a sale using Bead, the process generally follows these steps:

1. The sale is rung up in the merchant’s system.
2. The customer indicates they would like to pay using a digital payment method.
3. The customer selects from available tender options, such as cryptocurrency, digital wallets, or buy now pay later. The available tenders should match the merchant’s payment environment and fulfillment model.
4. Once the customer confirms they are ready to proceed, a payment prompt is generated for completion.
5. A real time exchange rate is locked in for the full payment amount when applicable.
6. The customer authorizes or submits the payment using their wallet or payment provider.
7. Bead detects the payment and updates the payment status.
8. If the payment enters `processing`, the payment is underway but not final. For most tenders this state may be brief. For BTC (on-chain) / Bitcoin on-chain, this state may last several minutes or longer.
9. The merchant’s system receives payment status updates through webhooks or status checks.
10. The merchant fulfills only after the payment reaches `completed`.
11. Funds are converted and settled to the merchant in US dollars according to the merchant’s settlement configuration.

Throughout this process, the merchant does not custody or manage cryptocurrency. Bead handles payment execution, conversion, and settlement, allowing businesses to offer modern payment options while maintaining a familiar settlement model.

### Related guides

* [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment)
* [Why do BTC (on-chain) payments take longer?](/faqs-and-troubleshooting/payments-faqs/why-do-btc-on-chain-payments-take-longer)
* [Tender Types](/reference-guide/enumerations-and-schemas/tender-types)
* [Payment Statuses](/payments/payment-statuses)
* [Payment Webhooks](/payments/payment-webhooks)
* [Crypto Wallet Flow and Amounts](/payments/crypto-wallet-flow-and-amounts)
* [Payment Scenarios](/reference-guide/payment-scenarios)


# Choosing Tender Types by Payment Environment

Tender types should be selected based on the payment experience, fulfillment timing, and the amount of customer or staff patience available in the flow.

Some tenders are appropriate for fast, real-time checkout. Others are better suited to invoices, high-ticket purchases, ecommerce orders, or payment flows where fulfillment naturally happens later.

This guide helps integrators decide which tender types to enable for a merchant, location, terminal, or payment flow.

### Overview

Not every tender type is a good fit for every payment environment.

When deciding which tenders to enable, consider:

* whether the customer is physically present
* whether goods or services are delivered immediately
* whether the merchant can wait before releasing goods
* whether staff can explain a pending payment state
* whether the payment amount is high enough to justify extra operational handling
* whether the customer can leave the payment screen before final completion
* whether the integration can track final payment status asynchronously

In general:

| Environment                     | Best tender behavior                                                                |
| ------------------------------- | ----------------------------------------------------------------------------------- |
| Fast in-person checkout         | Real-time or near-real-time completion                                              |
| High-touch physical commerce    | Near-real-time tenders, plus selected slower tenders when staff can manage the wait |
| Ecommerce with later shipment   | Real-time tenders and asynchronous tenders                                          |
| Invoices and service payments   | Real-time tenders and asynchronous tenders                                          |
| Instant digital delivery        | Real-time or near-real-time completion only                                         |
| High-ticket delayed fulfillment | Broader tender support, including BTC (on-chain) when appropriate                   |

### Tender timing categories

For integration planning, tender types can be grouped by expected customer experience.

#### Real-time or near-real-time tenders

These tenders are generally appropriate when the customer is expected to wait in the payment flow until the final result is available.

Examples may include:

* Bitcoin Lightning
* USDC network tenders
* supported wallet app tenders
* other tenders that normally complete in a short customer-facing window

These tenders are usually appropriate for:

* retail checkout
* service counters
* pickup counters
* digital checkout
* invoices
* ecommerce
* payment links

#### Longer-running tenders

BTC (on-chain), meaning Bitcoin on-chain, should be treated as a longer-running tender.

BTC (on-chain) can be appropriate when the merchant does not need to release goods or services immediately. It is less appropriate when the customer is standing at a counter waiting for immediate delivery.

When BTC (on-chain) enters `processing`, the transaction has been detected and is underway, but it should not be treated as final until the payment reaches `completed`.

### Physical payment environments

Physical environments require special care because the customer, merchant staff, and goods are often all present at the same time.

#### Fast in-person checkout

Examples:

* quick-service retail
* convenience checkout
* event concessions
* low-ticket service counters
* busy lines

Recommended tender approach:

| Tender type            | Recommendation                         |
| ---------------------- | -------------------------------------- |
| Near-real-time tenders | Recommended                            |
| BTC (on-chain)         | Usually not recommended                |
| Asynchronous tenders   | Avoid unless staff has a clear process |

Fast checkout environments usually should not enable BTC (on-chain) by default. The customer experience can become confusing if the transaction remains in `processing` while the customer is waiting for goods.

In this environment, the best tenders are the ones that complete quickly enough for the customer and staff to stay in the same flow.

#### High-touch physical commerce

Examples:

* jewelry stores
* luxury retail
* high-end electronics
* collectibles
* art sales
* large-ticket specialty retail

Recommended tender approach:

| Tender type            | Recommendation                  |
| ---------------------- | ------------------------------- |
| Near-real-time tenders | Recommended                     |
| BTC (on-chain)         | Appropriate in selected cases   |
| Asynchronous tenders   | Appropriate with staff training |

BTC (on-chain) can be reasonable in high-touch environments when the purchase amount is high enough and the merchant can explain the payment process.

For example, a jewelry store may be comfortable allowing a customer to initiate a BTC (on-chain) payment while staff continues the sales process. The merchant should still wait for `completed` before releasing the goods.

Use BTC (on-chain) here only when:

* staff understands the payment state
* the customer can wait or leave and return
* the merchant has a policy for pending payments
* goods are not released until payment completion

#### Vehicle sales and deposits

Examples:

* vehicle deposits
* motorcycle or boat deposits
* down payments
* reservation fees
* dealership invoices

Recommended tender approach:

| Tender type            | Recommendation                                   |
| ---------------------- | ------------------------------------------------ |
| Near-real-time tenders | Recommended                                      |
| BTC (on-chain)         | Good fit for deposits and delayed next steps     |
| Asynchronous tenders   | Good fit when operational workflow supports them |

BTC (on-chain) can work well for deposits or payments that do not require immediate vehicle release.

For example, a customer may place a deposit using BTC (on-chain), and the merchant can wait for the payment to complete before finalizing paperwork, holding inventory, or releasing the vehicle.

BTC (on-chain) is less appropriate when the payment is required immediately before the customer drives away with the vehicle.

#### Service businesses with pickup later

Examples:

* auto repair invoices
* equipment repair
* custom fabrication
* dry cleaning or tailoring
* repair shops

Recommended tender approach:

| Tender type            | Recommendation                   |
| ---------------------- | -------------------------------- |
| Near-real-time tenders | Recommended                      |
| BTC (on-chain)         | Good fit when paid before pickup |
| Asynchronous tenders   | Good fit                         |

BTC (on-chain) is often a good fit when the customer pays before arriving.

For example, a customer may pay a car repair invoice from home. The BTC (on-chain) payment can enter `processing`, and by the time the customer arrives to pick up the vehicle, the merchant may have received final confirmation.

In these flows, the customer does not need to remain on the payment screen until final completion. The integration should confirm that the transaction is underway and then notify the merchant or customer when payment reaches `completed`.

### Digital and online payment environments

Digital environments can support more tender types because the customer often does not need to receive goods or services immediately.

The key question is whether fulfillment happens now or later.

#### Ecommerce with later shipment

Examples:

* physical goods shipped later
* online retail
* specialty goods
* marketplace orders
* custom orders

Recommended tender approach:

| Tender type            | Recommendation |
| ---------------------- | -------------- |
| Near-real-time tenders | Recommended    |
| BTC (on-chain)         | Good fit       |
| Asynchronous tenders   | Good fit       |

BTC (on-chain) can work well for ecommerce when the order does not ship immediately.

The recommended flow is:

1. Customer chooses BTC (on-chain).
2. Customer sends the payment.
3. Payment moves to `processing`.
4. Customer is shown that the transaction has been detected.
5. Customer can leave the payment screen.
6. Merchant waits for `completed`.
7. Order is released for fulfillment after completion.

The customer should not be forced to stare at the payment page until the final status is reached. Instead, the integration should show an order confirmation or pending payment screen and rely on webhooks or status checks for the final result.

#### Digital goods and instant access

Examples:

* downloadable files
* white papers
* software license keys
* online courses with immediate access
* account upgrades
* gated content
* API credit purchases

Recommended tender approach:

| Tender type            | Recommendation                     |
| ---------------------- | ---------------------------------- |
| Near-real-time tenders | Recommended                        |
| BTC (on-chain)         | Usually not recommended            |
| Asynchronous tenders   | Avoid unless access can be delayed |

BTC (on-chain) is usually a poor fit for instant digital delivery.

The merchant should not grant access when the payment is only `processing`. If the customer expects immediate access, a long-running tender creates a poor experience and may increase support volume.

BTC (on-chain) may still be used if the integration clearly communicates that access will be granted only after payment completion. However, if immediate access is the core experience, faster tender types are usually a better fit.

#### Subscription or account-based services

Examples:

* account renewals
* SaaS subscriptions
* membership fees
* service credits
* account top-ups

Recommended tender approach:

| Tender type            | Recommendation                                           |
| ---------------------- | -------------------------------------------------------- |
| Near-real-time tenders | Recommended                                              |
| BTC (on-chain)         | Good fit when access does not need to change immediately |
| Asynchronous tenders   | Good fit with account-state handling                     |

BTC (on-chain) can work for subscriptions and account payments when the integration can hold the account state as pending until payment completion.

For example, a user may renew an annual account. The system can show “payment pending confirmation” and activate the renewal after the payment reaches `completed`.

Avoid BTC (on-chain) if the user must receive instant access, instant credits, or immediate quota increases.

### Invoice and payment link environments

Invoices are one of the best environments for broader tender support because payment completion does not always need to happen in the same session.

#### Business invoices

Examples:

* B2B invoices
* professional services
* wholesale payments
* milestone payments
* retainers

Recommended tender approach:

| Tender type            | Recommendation |
| ---------------------- | -------------- |
| Near-real-time tenders | Recommended    |
| BTC (on-chain)         | Good fit       |
| Asynchronous tenders   | Good fit       |

BTC (on-chain) is often appropriate for invoice payments because the merchant can reconcile payment completion asynchronously.

The integration should:

* show that payment was detected when the status reaches `processing`
* keep the invoice in a pending state
* mark the invoice as paid only after `completed`
* notify the merchant or payer when the payment is complete

#### Consumer invoices

Examples:

* repair invoices
* medical or dental invoices
* home services
* deposits
* reservations

Recommended tender approach:

| Tender type            | Recommendation                          |
| ---------------------- | --------------------------------------- |
| Near-real-time tenders | Recommended                             |
| BTC (on-chain)         | Good fit when service delivery can wait |
| Asynchronous tenders   | Good fit                                |

BTC (on-chain) can work well for consumer invoice payments when there is a natural gap between payment and fulfillment.

For example, a customer can pay from home before arriving to pick up goods or receive services.

### Decision framework

Use this decision framework before enabling BTC (on-chain) or another longer-running tender.

#### Step 1: Is the customer waiting in person?

If yes, be cautious with BTC (on-chain).

BTC (on-chain) may still be appropriate if the environment is high-touch, high-ticket, or operationally prepared for a longer wait.

#### Step 2: Are goods or services delivered immediately?

If yes, use tenders that complete quickly.

Do not release goods or grant access while a BTC (on-chain) payment is only `processing`.

#### Step 3: Can fulfillment wait?

If fulfillment can wait, BTC (on-chain) may be a good fit.

Examples include ecommerce shipment, invoice payment, service pickup, deposits, and high-ticket sales where staff can manage the experience.

#### Step 4: Can the integration handle asynchronous completion?

If using BTC (on-chain), the integration should be able to:

* detect `processing`
* show customer-facing pending payment messaging
* store the payment reference
* listen for webhooks or check status
* update the merchant order or invoice when the payment reaches `completed`
* prevent fulfillment before completion

If the integration cannot support asynchronous completion, BTC (on-chain) should not be enabled.

### Recommended tender fit matrix

| Payment environment                |     BTC (on-chain) fit | Recommended handling                                                                   |
| ---------------------------------- | ---------------------: | -------------------------------------------------------------------------------------- |
| Fast retail checkout               |                   Poor | Use near-real-time tenders only.                                                       |
| Busy service counter               |                   Poor | Avoid tenders that may leave the customer waiting.                                     |
| Jewelry or luxury retail           | Good in selected cases | Staff should explain the payment state and wait for completion before releasing goods. |
| Auto dealer deposit                |                   Good | Treat as pending until payment reaches `completed`.                                    |
| Auto repair invoice before pickup  |                   Good | Let the customer pay before arrival and confirm completion before release.             |
| Ecommerce with later shipment      |                   Good | Allow customer to move on after `processing`; ship after `completed`.                  |
| Digital download                   |                   Poor | Avoid unless delivery can be delayed.                                                  |
| Software license for immediate use |                   Poor | Prefer near-real-time tenders.                                                         |
| Subscription renewal               |            Conditional | Use if the account can remain pending until completion.                                |
| B2B invoice                        |                   Good | Reconcile asynchronously through webhooks or status checks.                            |
| Reservation or deposit             |                   Good | Confirm final status before guaranteeing fulfillment.                                  |

### Customer messaging guidance

When using BTC (on-chain), set expectations clearly.

#### When payment is still waiting for funds

Use language like:

```
Waiting for BTC payment.

Send the exact amount shown to the payment address. This page will update when the transaction is detected.
```

#### When payment reaches `processing`

Use language like:

```
Payment detected.

Your BTC payment is now processing on the Bitcoin network. This can take several minutes. You do not need to keep this page open.
```

#### When fulfillment is delayed

Use language like:

```
Your payment is pending confirmation.

The merchant will complete your order once the BTC payment is confirmed.
```

#### When goods cannot be released yet

Use language like:

```
The transaction has been detected, but payment is not complete yet.

The merchant can release the goods once the payment reaches completed status.
```

### Merchant operations guidance

Merchants using BTC (on-chain) should have an operational process for pending payments.

Recommended operational states:

| Merchant state            | Bead payment status                       | Meaning                                            |
| ------------------------- | ----------------------------------------- | -------------------------------------------------- |
| Awaiting payment          | `created` or equivalent pre-funding state | Customer has not yet sent funds.                   |
| Payment detected          | `processing`                              | BTC transaction is underway but not final.         |
| Paid                      | `completed`                               | Payment is complete and fulfillment may proceed.   |
| Payment failed or expired | Failed, expired, or canceled final state  | Merchant should not fulfill without a new payment. |

For physical environments, staff should know:

* what `processing` means
* that `processing` is not final approval
* whether the customer may leave and return
* how the customer will be notified
* when goods may be released

For digital environments, systems should know:

* whether to hold the order
* whether to show pending payment status
* when to grant access
* how to notify the customer
* how to retry or recover if payment does not complete

### Integration guidance

When building tender selection into a checkout or payment flow:

1. **Filter tenders by environment**\
   Do not show every tender everywhere. Match tender options to the merchant’s operational model.
2. **Use terminal or location context**\
   A physical terminal may need a different tender set than a virtual terminal for the same merchant.
3. **Use amount context**\
   Slower tenders may make more sense for high-ticket payments than low-ticket payments.
4. **Use fulfillment context**\
   If fulfillment is delayed, broader tender support is usually reasonable. If fulfillment is immediate, prioritize fast tenders.
5. **Use customer messaging by tender**\
   BTC (on-chain) needs different messaging than near-real-time tenders.
6. **Use webhooks or status checks**\
   Long-running payment flows should not depend on the customer keeping a browser tab open.
7. **Fulfill only after final status**\
   Treat `completed` as the fulfillment trigger.

### BTC (on-chain) guidance

BTC (on-chain) is best understood as a longer-running Bitcoin on-chain tender.

Use BTC (on-chain) when:

* the merchant can wait for payment completion
* the customer does not need immediate delivery
* the integration can support asynchronous confirmation
* the payment amount or merchant preference justifies the additional wait
* staff or system messaging can clearly explain the pending state

Avoid BTC (on-chain) when:

* the customer is waiting in a fast checkout line
* the merchant must release goods immediately
* the product is a digital good delivered instantly
* the system cannot hold fulfillment until payment completion
* the customer experience depends on immediate final approval

When BTC (on-chain) reaches `processing`, the recommended experience is:

1. tell the customer the payment has been detected
2. explain that Bitcoin on-chain confirmation may take several minutes
3. move the customer to an order, invoice, or status page
4. notify the merchant or customer when the payment reaches `completed`
5. fulfill only after `completed`

### Practical examples

#### Example 1: Fast retail checkout

A customer is buying a low-ticket item in a busy store.

Recommended tender setup:

* Enable near-real-time tenders.
* Do not enable BTC (on-chain) by default.
* Avoid payment methods that require staff to monitor a long pending state.

Why:

The customer expects to pay and leave. A longer BTC (on-chain) confirmation window may create line delays and staff confusion.

#### Example 2: Jewelry store

A customer is purchasing a high-value item.

Recommended tender setup:

* Enable near-real-time tenders.
* Consider enabling BTC (on-chain).
* Train staff to explain pending confirmation.
* Release goods only after `completed`.

Why:

The ticket size and high-touch service model may justify a longer payment process.

#### Example 3: Ecommerce order with shipping

A customer buys a physical product online that will ship later.

Recommended tender setup:

* Enable near-real-time tenders.
* Enable BTC (on-chain) if the merchant accepts asynchronous confirmation.
* Put the order into pending payment confirmation when BTC (on-chain) is `processing`.
* Ship only after `completed`.

Why:

The customer does not need immediate physical delivery, so the payment can complete before fulfillment.

#### Example 4: Digital white paper

A customer buys a downloadable report and expects immediate access.

Recommended tender setup:

* Enable near-real-time tenders.
* Avoid BTC (on-chain) unless the download can be delayed.
* Grant access only after `completed`.

Why:

BTC (on-chain) may leave the customer waiting, and access should not be granted while the payment is still `processing`.

#### Example 5: Auto repair invoice

A customer pays a repair invoice from home before picking up their vehicle.

Recommended tender setup:

* Enable near-real-time tenders.
* Enable BTC (on-chain) if the shop supports pending payment workflows.
* Notify the shop when payment reaches `completed`.

Why:

The transaction can process before the customer arrives, making BTC (on-chain) a reasonable option.

### Recommended default policy

Use this as a default tender-selection policy:

| Flow type                              | Default BTC (on-chain) policy                               |
| -------------------------------------- | ----------------------------------------------------------- |
| Fast physical checkout                 | Disabled                                                    |
| High-ticket physical checkout          | Optional                                                    |
| Luxury or high-touch physical checkout | Optional                                                    |
| Ecommerce with shipment                | Enabled if merchant supports pending orders                 |
| Invoice payments                       | Enabled                                                     |
| Service pickup payments                | Enabled when paid before pickup                             |
| Instant digital goods                  | Disabled                                                    |
| Immediate account access               | Disabled unless access can remain pending                   |
| Deposits and reservations              | Enabled if final confirmation is required before commitment |

### Summary

Tender selection should follow the customer experience and fulfillment model.

BTC (on-chain) is not a bad tender type. It is a tender type that requires the right environment.

It works best when:

* payment confirmation can happen asynchronously
* the merchant can wait before fulfillment
* the customer does not need to remain on the payment screen
* the integration uses webhooks or status checks
* the merchant releases goods or services only after `completed`

It works poorly when:

* the customer expects immediate goods
* staff cannot manage a pending state
* the product is delivered instantly
* the integration cannot hold fulfillment

Use fast tenders for fast checkout. Use BTC (on-chain) where delayed confirmation fits the business process.

### Related pages

* [Why do BTC (on-chain) payments take longer?](/faqs-and-troubleshooting/payments-faqs/why-do-btc-on-chain-payments-take-longer)
* [Payment Statuses](/payments/payment-statuses)
* [Payment Webhooks](/payments/payment-webhooks)
* [Create Payment](/payments/create-payment)
* [Crypto Wallet Flow and Amounts](/payments/crypto-wallet-flow-and-amounts)
* [Crypto Tender Types](/reference-guide/enumerations-and-schemas/tender-types)


# Payment Scenarios

## Payment Scenarios

### Purpose

Payment Scenarios explains how to handle non-happy-path outcomes after a payment has been created.

Use this section when a payment does not follow the standard completed flow, or when your business team needs guidance on what to do next after a real-world exception such as an underpayment, overpayment, reclaimable balance, or return request.

This section is not the primary home for creating payments, checking basic payment status, or configuring payment webhooks.

* Go to [Payments](/payments) for payment creation, payment statuses, wallet flows, payment webhooks, receipts, and test transactions.
* Go to [Operational Guides](/reference-guide/operational-guides) for supplemental production guidance such as webhook hardening and wallet compatibility.
* Go to [Reporting](/reporting) and [Settlement](/settlement) when you need historical or financial records across many payments.

### What you’ll find here

| Page                                                                                                   | When to read it                                                                                                                           |
| ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| [Under- and Over-Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling) | The customer sends less or more crypto than requested and the payment ends as `underpaid` or `overpaid`.                                  |
| [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto)      | A payment ends as `underpaid`, `overpaid`, `expired`, `invalid`, or `cancelled`, and unconverted funds must be reclaimed by the customer. |
| [Refunds for Crypto and Wallet Payments](broken://pages/tww8bW3XqcNID1T5P49a)                          | You need return-policy guidance for completed crypto or wallet payments and need to understand how refunds differ from reclaim.           |

### How to use this section

1. Start with the payment outcome reported by your webhook or payment status response.
2. If the payment is `underpaid` or `overpaid`, open [Under- and Over-Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling).
3. If unconverted funds remain, open [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto).
4. If the payment completed and the business wants to return funds afterward, open [Refunds for Crypto and Wallet Payments](broken://pages/tww8bW3XqcNID1T5P49a).
5. Apply the business handling in your UI, support workflow, and internal reconciliation process.

### Scenario routing guide

| Payment outcome                                                      | What it means for the business                                                                         |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `completed`                                                          | Payment succeeded. Follow your normal completed-payment workflow.                                      |
| `underpaid`                                                          | Do not fulfill. The customer must reclaim the funds and start a new payment if they still want to pay. |
| `overpaid`                                                           | Treat the requested amount as paid. Any excess is handled through reclaim.                             |
| `expired`                                                            | Do not fulfill. If funds later arrive and remain unconverted, they are handled through reclaim.        |
| `invalid`                                                            | Do not fulfill. Any eligible unconverted funds are handled through reclaim.                            |
| `cancelled`                                                          | Do not fulfill. Any remaining unconverted funds may be reclaimed.                                      |
| Customer wants money back after a completed crypto or wallet payment | Follow the refund or return guidance for completed payments, which is separate from reclaim.           |

### Related sections

| Section                                                   | Use it for                                                                                |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| [Payments](/payments)                                     | Core payment API flows, hosted checkout, payment statuses, receipts, and payment webhooks |
| [Operational Guides](/reference-guide/operational-guides) | Supplemental production guidance such as webhook consumer design and wallet QA            |
| [Reporting](/reporting)                                   | Search and review payment history across terminals, merchants, or partners                |
| [Settlement](/settlement)                                 | Understand what settled for the merchant and what did not                                 |


# Under and Over Payment Handling

Some payment outcomes require follow-up before the merchant can decide whether to fulfill, close the order, or direct the customer to another flow. This page explains how to handle payments that end as underpaid or overpaid. These scenarios are most relevant in crypto wallet payment flows, where the customer controls the amount they send.

### When this page applies

Use this page when the final payment outcome is `underpaid` or `overpaid`.

If the payment ends as `expired`, `invalid`, or `cancelled`, use the reclaim guidance instead.

If the payment completed successfully and the customer later wants money back, use the refund and return guidance instead.

### What these outcomes mean

**underpaid**

The customer sent less than the requested amount. This should be treated as an incomplete payment. The merchant should not fulfill the order based on that payment.

**overpaid**

The customer sent more than the requested amount. This should be treated as an incomplete payment. The full crypto amount paid by the customer remains unconverted and is returned to the payer through Bead's reclaim process. No portion of an overpaid payment is converted or settled to the merchant.

### How to handle an underpaid payment

When a payment ends as `underpaid`:

* Do not fulfill the order
* Do not treat the payment as complete
* Do not ask the customer to continue using the same payment session
* Direct the customer to reclaim any eligible unconverted funds
* Create a new payment if the customer still wants to complete the purchase

**Recommended business handling**

Your support, checkout, or order management flow should clearly tell the customer that the payment did not complete successfully because the amount received was lower than the amount requested. If the customer still wants to proceed, generate a new payment and direct them to that new payment flow rather than trying to recover the original payment session.

### How to handle an overpaid payment

When a payment ends as `overpaid`:

* Do not fulfill the order
* Do not treat the payment as complete
* Do not ask the customer to continue using the same payment session
* Direct the customer to reclaim the full unconverted amount
* Create a new payment if the customer still wants to complete the purchase

**Recommended business handling**

Your customer-facing messaging should make it clear that the payment did not complete because the amount received exceeded the amount requested. The full crypto amount will be returned through Bead's reclaim process. If the customer still wants to proceed, generate a new payment and direct them to a new payment flow.

### Reclaim versus refund

It is important to keep these flows separate.

**Reclaim** applies when a payment does not settle cleanly as intended and unconverted funds remain associated with the payment outcome. This is the correct follow-up for `underpaid`, `overpaid`, `expired`, `invalid`, and `cancelled`.

**Refund or return** guidance applies only after a completed payment, when the merchant later decides to return value to the customer under its normal return policy. Do not treat underpaid or overpaid handling as the same thing as a post-payment refund.

### Integration guidance

Your integration should use the payment outcome to decide what happens next.

**For underpaid**

* Mark the order or invoice as unpaid or incomplete
* Stop fulfillment
* Tell the customer the payment did not complete for the requested amount
* Send the customer to reclaim guidance if applicable
* Generate a new payment if they still want to proceed

**For overpaid**

* Mark the order or invoice as unpaid or incomplete
* Stop fulfillment
* Tell the customer the payment did not complete because the amount received exceeded the amount requested
* Send the customer to reclaim guidance so the full unconverted amount can be returned
* Generate a new payment if they still want to proceed

### Webhooks and status handling

Use the final payment outcome as the source of truth for your workflow. In practice, that means your integration should:

* Listen for payment status changes through webhooks
* Update your internal order or invoice state when the payment becomes `underpaid` or `overpaid`
* Avoid building merchant logic that assumes every customer will send the exact requested amount

For operational follow-up or support workflows, you can also retrieve the latest payment record directly when needed.

### Customer messaging guidance

Clear customer messaging reduces support friction.

**Suggested guidance for underpaid payments**

> Your payment did not complete because the amount received was lower than the amount requested. Please follow the reclaim instructions for the original transfer. If you still want to complete your purchase, start a new payment.

**Suggested guidance for overpaid payments**

> Your payment did not complete because the amount received was higher than the amount requested. The full amount you sent will be returned through the reclaim process. If you still want to complete your purchase, start a new payment.

### Fulfillment guidance

Use the payment outcome to determine fulfillment.

| Payment outcome                   | Fulfillment behavior                                             |
| --------------------------------- | ---------------------------------------------------------------- |
| `underpaid`                       | Do not fulfill                                                   |
| `overpaid`                        | Do not fulfill                                                   |
| `completed`                       | Fulfill normally                                                 |
| `expired`, `invalid`, `cancelled` | Do not fulfill. Follow the applicable reclaim or exception flow. |

### Best practices

* Treat `underpaid` and `overpaid` as non-successful final states that both require reclaim and a new payment to retry
* Keep reclaim handling separate from merchant refund handling
* Create a new payment for any retry after an `underpaid` or `overpaid` outcome
* Make your webhook processing idempotent
* Store the requested amount, final outcome, and order decision in your internal records for support and reconciliation

### Related pages

* [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto)
* [Payment Statuses](/payments/payment-statuses)
* [Payment Webhooks](/payments/payment-webhooks)


# Reclaiming Unconverted Crypto

## Reclaiming Unconverted Crypto

Some payment outcomes result in crypto being received by Bead but not converted. In these cases, Bead returns the unconverted crypto to the payer through the reclaim flow.

This page explains when reclaim happens, how email is captured, what the customer experiences, and how integrators should handle these scenarios.

### When reclaim happens

Reclaim can be required when a payment ends in one of these statuses:

| statusCode  | What happens                                                                                                    | What is reclaimable                                                              |
| ----------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `underpaid` | The payer sent less than the requested amount. The transaction is not completed.                                | The full amount received is reclaimable because nothing is converted or settled. |
| `overpaid`  | The payer sent more than the requested amount. The transaction is not completed.                                | The full amount received is reclaimable because nothing is converted or settled. |
| `expired`   | No valid payment was completed before the payment window expired.                                               | Any funds received remain unconverted and are reclaimable.                       |
| `invalid`   | Irregular event such as multiple transactions, unsupported asset, compliance block, or extreme market movement. | Any funds received remain unconverted and are reclaimable.                       |
| `cancelled` | The payment was cancelled before completion.                                                                    | Any funds received remain unconverted and are reclaimable.                       |

Reclaim returns crypto in the same asset and network that was received, subject to the constraints described below.

### Why reclaim exists

In wallet based crypto payments, the payer often enters the crypto amount manually in their wallet app after scanning a QR code. Mistyped amounts and other non-standard conditions can cause funds to be received but not eligible for conversion and settlement.

Reclaim provides a consistent and compliant way to return unconverted crypto to the payer without requiring the integrator to build a custom crypto return flow.

### How the reclaim flow starts

Bead initiates reclaim after:

* A payment ends in a reclaimable status
* Bead has an email address for the payer

When email is available, Bead sends the payer an email that contains instructions and a reclaim link.

### Email capture and the refundEmail field

Bead uses an email based reclaim flow. If the integrator provides an email in the original payment request, Bead uses it to send reclaim instructions.

In the Payments API, the email field used for this purpose is named `refundEmail`. The field name stays the same, but in this context it means where reclaim instructions are sent, not a merchant initiated refund.

| Environment                         | Typical behavior                                                                                                                                                        |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Digital or virtual checkout         | The integrator usually provides customer email in the Create Payment request, so reclaim instructions can be sent automatically.                                        |
| Physical or terminal based checkout | Customer email is often not provided. When an underpaid or overpaid outcome occurs, the hosted payment page prompts the payer to enter an email so reclaim can proceed. |

### What the customer experiences

The reclaim experience is driven by the email link and the hosted reclaim flow. A typical customer journey looks like this:

1. Customer pays via wallet and the payment ends as `underpaid`, `overpaid`, `expired`, `invalid`, or `cancelled`
2. Bead emails reclaim instructions when an email address is available
3. Customer opens the reclaim link
4. Customer confirms a destination wallet address for the return
5. Bead returns the unconverted crypto to the provided address

For both `underpaid` and `overpaid`, the customer receives the full amount they sent, as the full amount remains unconverted and is eligible for reclaim.

### Integrator responsibilities

Integrators do not implement the crypto return process directly, but you should handle the customer experience and internal state correctly.

| statusCode                        | Recommended behavior                                                                                      |
| --------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `underpaid`                       | Treat as not successful payment. Do not fulfill. Create a new payment if the customer wants to try again. |
| `overpaid`                        | Treat as not successful payment. Do not fulfill. Create a new payment if the customer wants to try again. |
| `expired`, `invalid`, `cancelled` | Treat as not successful payment. Do not fulfill.                                                          |

### Suggested UI messaging

You can adapt the wording below for your checkout UI or support flows.

**Underpaid**

> Payment not completed. The amount sent was less than requested. The crypto that was sent will be returned through the reclaim process. Please start a new payment to try again.

**Overpaid**

> Payment not completed. The amount sent was more than requested. The full crypto amount you sent will be returned through the reclaim process. Please start a new payment to try again.

**Expired, invalid, cancelled**

> Payment not completed. If any crypto was sent, it will be returned through the reclaim process.

### Providing the reclaim email in Create Payment

If you can, provide the reclaim email up front so the customer does not need to re-enter it later. Use the `refundEmail` field in the Create Payment request.

If you also send a `customer` object, ensure it is valid if present. Providing partial customer data may trigger required field validation.

### Timing and reclaim windows

Reclaim timing is controlled by two concepts.

**Self-serve reclaim window**

By default, the self-serve reclaim link is valid for a limited window starting from the payment created timestamp.

|                |                                           |
| -------------- | ----------------------------------------- |
| Default window | 7 days from the payment created timestamp |

If the payer attempts reclaim after the self-serve window expires, the reclaim link may no longer work and the payer must contact support.

**Long stop handling**

Unclaimed funds are not held indefinitely. After a longer holding period, unclaimed funds may be moved into Bead controlled handling, subject to policy, compliance requirements, and operational constraints.

|                          |                                            |
| ------------------------ | ------------------------------------------ |
| Typical long stop period | 30 days from the payment created timestamp |

The 7 day window is the standard self-serve experience. The 30 day period is a longer term handling boundary. If a payer misses the 7 day window, they should contact support as soon as possible and well before the long stop period.

### Constraints and important notes

**Asset and network**

Reclaim returns crypto in the original asset and network that was received. The customer must provide a compatible destination address.

**Fees and exact amounts**

Network fees may apply to the return transaction. The returned amount may be reduced by applicable network fees, depending on the asset, network, and reclaim method.

**Customer support visibility**

For operational tracking, integrators should store and be ready to share:

* `trackingId`
* `statusCode`
* Amount requested
* Amount received
* Customer email used for reclaim instructions, if provided

### Frequently asked questions

**Can the payer send additional funds to fix an underpayment?**

No. Underpaid payments are treated as not completed. If the payer still wants to pay, the integrator should create a new payment and direct the payer to the new hosted payment page.

**Do integrators need to implement the reclaim return transaction?**

No. Bead handles the crypto return flow. Integrators should surface clear messaging and route support questions appropriately.

**If the payer did not provide an email, can reclaim still happen?**

Yes. In many physical flows, the hosted payment page will prompt the payer for an email when reclaim is required. Once collected, Bead sends the reclaim instructions. If the payer closes the flow before providing an email, reclaim cannot start until an email address is captured.

### Related pages

* [Payment Statuses](/payments/payment-statuses)
* [Under and Over Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling)
* [Payment Webhooks](/payments/payment-webhooks)


# Refunds for Crypto and Wallet Payments

Crypto, digital wallet, and BNPL payments through Bead settle like cash. When a customer completes a payment using one of these tenders, the transaction closes at the point of sale. The funds are converted, settled, and deposited into the merchant's bank account as USD. There is no underlying card network, no issuing bank, and no chargeback rail.

As a result, there is no API-level refund capability for completed transactions. This page explains why, and what merchants and integrators should do instead.

This page covers post-completion return scenarios only. If you are looking for guidance on payments that did not complete successfully, see [Under- and Over-Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling) and [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto).

#### How these payments settle

When a customer pays with crypto, a digital wallet, or BNPL through Bead, the following happens:

1. The customer's payment is received and confirmed on the relevant network or rail.
2. For crypto, the digital asset is converted to USD at the time of payment. The original asset is no longer held.
3. The USD value, net of fees, is settled to the merchant's bank account via ACH, typically on the next business day.

By the time a merchant wants to issue a refund, the original payment has already been converted and deposited. There is no crypto balance, wallet balance, or BNPL transaction to reverse. The funds are in the merchant's bank account as USD.

#### Why there is no refund API

**The asset no longer exists in returnable form**

Once a crypto payment is converted to USD and settled, the specific digital assets the customer sent are gone. Sending crypto back to a customer would require sourcing new assets, which is structurally a new outbound transfer, not a reversal of the original transaction.

**Outbound crypto transfers carry regulatory requirements**

Under the Bank Secrecy Act and FinCEN's Funds Travel Rule, outbound transfers of digital assets above regulatory thresholds require identity verification, sanctions screening, and in some cases Suspicious Activity Report filing. Enabling an API-level refund for crypto would require building a compliant outbound crypto transfer system with meaningful regulatory overhead, not a simple reversal endpoint.

This is also why Bead's reclaim process for failed or exception payments exists as a separate controlled flow. It handles the narrow case of returning funds that were never successfully settled, and does so with the appropriate compliance controls in place.

**Crypto in and crypto out creates financial crime risk**

Allowing a merchant to receive a crypto payment, convert it to USD, and then trigger a crypto payout back to a customer creates a structure that regulators recognize as a potential money laundering or asset conversion vehicle. Bead operates as a software and agent layer, not a money transmitter or exchange. Enabling this flow would expose Bead and its sponsor bank to Money Services Business and Money Transmitter regulatory risk.

**The same applies to digital wallets and BNPL**

Digital wallet payments such as PayPal and Venmo and BNPL payments such as Klarna settle to the merchant as USD through Bead's settlement infrastructure. There is no open wallet session or BNPL loan record that Bead can reverse after settlement. Those rails do not expose a merchant-initiated reversal capability to Bead at the point-of-sale integration layer.

#### What merchants should do

Because these tenders settle like cash, merchants should apply their standard cash return policy to any customer requesting a refund on a Bead-tendered transaction.

That policy may include any of the following:

* Cash back from the register or till
* ACH or check issued from the merchant's bank account
* Store credit, gift card, or credit memo issued in the merchant's own system
* Exchange for another item or service

The appropriate refund method is determined entirely by the merchant's return policy, not by the payment method used at the time of purchase. Merchants should communicate their return policy clearly to customers at the time of purchase, particularly for high-value transactions, to avoid friction at the point of return.

#### What integrators should know

There is no refund endpoint. Do not build against one.

The `fullyRefunded` and `partiallyRefunded` payment status codes are reserved for future use and are not currently returned by the API.

The reclaim process handles a separate scenario: returning unconverted funds to a customer when a payment fails due to underpayment, overpayment, expiration, or cancellation. This is not a merchant-initiated refund. It is an exception flow managed by Bead for payments that never successfully completed. See [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto) for details on that flow.

For reporting and reconciliation, use the Reporting and Settlement APIs to identify completed transactions. Refund events handled outside of Bead through cash, ACH, or store credit should be tracked in your own order management or point-of-sale system.

#### Related pages

* [Under- and Over-Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling)
* [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto)
* [Payment Statuses](/payments/payment-statuses)
* [Receipts](/payments/receipts)
* [Reporting](/reporting)
* [Settlement](/settlement)


# Expired Payment Handling

#### Purpose

This page explains what happens when a payment reaches the `expired` status, how to handle it in your integration, and what the customer experiences.

Use this page when a payment status check or webhook returns `expired` and you need to know what to do next.

#### What causes an expired payment?

Each Bead payment is backed by a crypto quote — a fixed conversion rate valid for a limited window. If the customer does not complete the wallet transfer before the quote window closes, the payment moves to `expired`.

The hosted payment page gives the customer a short window to request a new quote before the session ends. If the customer does not act within that window, the payment is automatically marked `expired`.

Expiry is not caused by merchant or integrator action. It is driven entirely by the quote window timing out.

#### What happens to funds sent after expiry?

If a customer sends funds after the payment has expired:

* the funds are not converted
* they remain unconverted and are returned to the payer through Bead's reclaim process
* Bead emails reclaim instructions to the payer if an email address is available

The integrator does not need to implement the crypto return flow. See [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto) for full details on reclaim timing, windows, and the customer experience.

#### Integrator handling

**Order and fulfillment**

* Treat `expired` as a terminal non-success state.
* Do not fulfill the order.
* Do not reuse the expired payment URL — it cannot be resumed or extended.
* If the customer still wants to pay, create a new payment and direct them to the new hosted payment URL.

**Internal state mapping**

Map `expired` to a non-success internal state such as:

* Payment expired
* Session timed out
* Awaiting new payment

Do not map `expired` to paid, approved, or any fulfillable state.

**Webhook handling**

When your webhook consumer receives a payload with `statusCode: "expired"`:

1. Verify the webhook signature.
2. Persist the event.
3. Update your internal order or invoice to a non-success state.
4. Surface appropriate messaging to the customer.
5. Return `2xx` promptly.

Use `trackingId` + `statusCode` as your idempotency key. See [Payment Webhooks](/payments/payment-webhooks) for signature verification and processing guidance.

#### Customer experience

**In the hosted UI**

When the quote is about to expire, the hosted payment page gives the customer a short window to request a new quote. If no action is taken, the session ends and the payment moves to `expired`.

The customer cannot resume the expired session. They must start a new payment.

**If funds were sent after expiry**

Bead initiates reclaim after the payment expires if funds are received. If a `refundEmail` was included in the original payment request, Bead emails the customer reclaim instructions automatically. If no email was provided, the hosted page prompts the customer for an email when reclaim is required.

The self-serve reclaim window is 7 days from the payment created timestamp. Customers who miss that window should contact support.

#### Suggested UI messaging

Use clear, direct messaging so the customer knows what happened and what to do next.

**When the payment expires before the customer pays:**

```
Your payment session expired before a payment was detected. To complete your
purchase, please start a new payment.
```

**When the customer may have sent funds after expiry:**

```
Your payment session expired before a payment was detected. If you sent funds
after the session ended, they will be returned to you through the reclaim
process. To try again, please start a new payment.
```

#### Scenario routing

| Situation                                                                | What to do                                                                                   |
| ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| Payment reached `expired`, no funds sent                                 | Create a new payment if the customer wants to try again                                      |
| Payment reached `expired`, customer may have sent funds late             | Direct customer to check for a reclaim email; create a new payment if they want to try again |
| Customer sent funds just before expiry but payment still shows `expired` | Funds are handled through reclaim; create a new payment for the purchase                     |
| Customer wants to resume the expired session                             | Not possible — create a new payment                                                          |

#### Related pages

* [Payment Statuses](/payments/payment-statuses)
* [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto)
* [Under- and Over-Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling)
* [Payment Webhooks](/payments/payment-webhooks)
* [Why Did My Payment Expire, and What Happens Next?](/faqs-and-troubleshooting/payments-faqs/why-did-my-payment-expire-and-what-happens-next)


# Operational Guides

### Purpose

Operational Guides contains supplemental implementation guidance for running a payments integration in production.

Use this section for practical topics that support the core Payments documentation, such as webhook consumption, operational hardening, and end-to-end wallet testing.

This section is not the primary home for core API usage, payment status behavior, or onboarding workflows.

* Go to [Payments ](/payments)for payment creation, payment statuses, receipts, payment webhooks, and testing flows.
* Go to [Payment Scenarios](/reference-guide/payment-scenarios) for non-happy-path payment outcomes such as underpaid, overpaid, reclaim, and refund or return guidance.
* Go to [Onboarding](/onboarding) for merchant application submission, signing, status tracking, and onboarding webhooks.

### What you’ll find here

| Page                                                                                       | What it covers                                                                                                               |
| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| [Webhook Event Reference](/reference-guide/operational-guides/webhook-event-reference)     | Supplemental reference for event payloads, signature verification, retry behavior, idempotency, and webhook consumer design. |
| [Compatible Crypto Wallets](/reference-guide/operational-guides/compatible-crypto-wallets) | Wallets to validate during QA, expected wallet capabilities, and practical caveats for testing and launch readiness.         |

### How to use this section

1. Start in [Payments](/payments) to implement payment creation, hosted checkout, status handling, and primary payment webhook configuration.
2. Use [Webhook Event Reference](/reference-guide/operational-guides/webhook-event-reference) when hardening your webhook listener for production, especially around verification, retries, logging, and idempotency.
3. Use [Compatible Crypto Wallets](/reference-guide/operational-guides/compatible-crypto-wallets) when planning QA, demos, and support expectations across wallet types.
4. Use [Payment Scenarios](/reference-guide/payment-scenarios) when the question is outcome-driven, such as what to do when a payment is underpaid, overpaid, expired, invalid, cancelled, or requires reclaim handling.

### Related sections

| Section                                                 | Use it for                                                                             |
| ------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| [Payments](/payments)                                   | Core payment API flows, hosted checkout, status checks, receipts, and payment webhooks |
| [Payment Scenarios](/reference-guide/payment-scenarios) | Outcome-specific handling for non-happy-path payment states                            |
| [Onboarding](/onboarding)                               | Merchant application flows and onboarding event handling                               |
| [Core Concepts](/reference-guide/core-concepts)         | Cross-cutting concepts such as authentication, environments, and rate limits           |


# Compatible Crypto Wallets

Bead is wallet agnostic. Any wallet that can:

1. Scan the QR code provided by the hosted payment page, and
2. Broadcast the correct asset on the correct network to the destination address

can be used to complete a payment.

Bead Sandbox crypto payments use live blockchain networks. Test payments require real assets and real network fee tokens. Test amounts should remain small.

For USDC on Base and USDC on Solana, the minimum Bead payment amount is $1.00 USD. This minimum is separate from any live-network fee the payer’s wallet may require to submit the transaction.

### Asset and wallet matrix

Use this table as the primary reference for supported assets, networks, tender keys, and example wallets. The wallet lists are examples, not an exhaustive set, and are ordered roughly by consumer usage at the time of writing.

#### Bitcoin-based tenders

| Asset / network   | Tender key          | Example wallets, approximate usage order                              | Notes                                                                                                                                                                                                                                                                                                                                                             |
| ----------------- | ------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bitcoin on-chain  | `bitcoin`           | Base app, formerly Coinbase Wallet; Phantom; Trust Wallet; BlueWallet | Use Bitcoin mainnet. These wallets are widely used for on-chain BTC and allow sending to standard Bitcoin addresses. Customers must hold enough BTC to cover both the invoice amount and network fees, which are deducted in BTC. Sandbox and Production both use the live Bitcoin network.                                                                       |
| Bitcoin Lightning | `bitcoin_Lightning` | Wallet of Satoshi, Phoenix, Muun                                      | Wallet of Satoshi, custodial, and Lightning-capable self-custody wallets such as Phoenix and Muun are popular choices for Lightning payments. The wallet must support Lightning invoices, for example BOLT-11, or LNURL QR codes and be able to pay the invoice presented by the hosted payment page. Sandbox and Production both use the live Lightning network. |

#### USDC tenders

| Asset / network | Tender key   | Example wallets, approximate usage order                            | Notes                                                                                                                                                                                                                                                                                                                                                                                                                 |
| --------------- | ------------ | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| USDC on Base    | `usdcBase`   | Phantom, Base app, formerly Coinbase Wallet; MetaMask; Trust Wallet | Use USDC on the Base network. ETH on Base is required to pay network fees. Sandbox and Production both use Base mainnet with real USDC and real ETH, so test amounts should remain small. The minimum Bead payment amount is $1.00 USD. Wallets must clearly distinguish USDC on Base from USDC on other networks.                                                                                                    |
| USDC on Solana  | `usdcSolana` | Phantom, Solflare, Ledger Live                                      | Use USDC on Solana mainnet. SOL on Solana is required to pay network fees. Sandbox and Production both use Solana mainnet with real USDC and real SOL, so test amounts should remain small. The minimum Bead payment amount is $1.00 USD. Phantom is the most widely used Solana wallet and is the recommended default. Solflare and hardware wallets such as Ledger also provide strong support for Solana and USDC. |

As new assets and networks are added, this table will be updated. Treat any tender key or network that is not listed here as unsupported until it appears in this matrix.

### Minimum payment amounts and fee tokens

Minimum payment amounts can vary by tender. Network fee assets are separate from the Bead payment amount and are required by the payer’s wallet to submit the transaction.

| Tender key          |                   Bead minimum payment amount | Fee token needed by payer wallet                  |
| ------------------- | --------------------------------------------: | ------------------------------------------------- |
| `usdcBase`          |                                     $1.00 USD | ETH on Base                                       |
| `usdcSolana`        |                                     $1.00 USD | SOL on Solana                                     |
| `bitcoin`           | Confirm any program-specific limits with Bead | BTC                                               |
| `bitcoin_Lightning` | Confirm any program-specific limits with Bead | BTC through the Lightning wallet and routing path |

Wallet apps, exchanges, and on-ramp providers may have their own minimum purchase amounts or fees. Those limits are separate from Bead payment minimums.

### Minimum wallet capabilities

A wallet used with Bead should meet all of the following core capabilities.

| Capability        | Why it matters                                                                                                                                                                                      |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| QR scanning       | Bead encodes the payment address and, for Lightning, invoice details in a QR code. The wallet must be able to read the QR and populate the send screen.                                             |
| Exact amount send | Customers must match the requested crypto amount to avoid underpaid or overpaid states. Small differences caused by “max” send or manually edited amounts can change how the payment is treated.    |
| Network selection | Assets such as USDC exist on multiple networks. The wallet must allow the user to select the network requested by the hosted payment page, for example Base, Solana, Bitcoin mainnet, or Lightning. |
| Timely broadcast  | Payment windows are timebound. Wallets that queue or significantly delay broadcasts can cause otherwise valid payments to expire before they are observed on chain or on the Lightning network.     |

### Unsupported and cautionary wallet types

Some wallet and account types introduce limitations that make them unsuitable for Bead payments.

#### Custodial and closed systems

| Category                                             | Reason                                                                                                                                                                                                                                                                                                                        |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Purely custodial accounts, such as exchange balances | Exchange accounts hold assets on behalf of the user and may not support sending directly to arbitrary external addresses or Lightning invoices in a real-time checkout flow. Withdrawals can be delayed or batched, which does not align with Bead’s payment windows, even if the exchange can link to a self-custody wallet. |
| Closed or internal-transfer-only solutions           | Systems that only support transfers between users of the same platform cannot broadcast on-chain or Lightning transactions to Bead’s destination address.                                                                                                                                                                     |

#### Limited network support

| Category                              | Reason                                                                                                                                                                                                                                                      |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Wallets without the required networks | Wallets that cannot connect to Base, Solana, Bitcoin mainnet, or Lightning, as applicable, cannot be used for those tenders. For USDC, wallets must support Base mainnet or Solana mainnet, since Sandbox and Production both use live networks and assets. |

If a wallet or service does not allow the user to control a self-custody wallet and send directly to an external address or Lightning invoice, it should be treated as unsupported for Bead payments.


# Merchant Onboarding Guide

This guide explains the operational flow for onboarding a merchant with Bead. It complements the API reference and schema pages and is intended for integrators, solution engineers, and project managers.

### Who this is for

* Integrators submitting merchant data
* Resellers coordinating merchant onboarding and credentials
* Teams planning downstream provisioning of locations and terminals

### Prerequisites

* OAuth access to the non production or production environment
* Partner or integrator account configured
* Merchant contact name and email ready
* Basic business profile details gathered

### End to end flow

| Step | Actor      | Interface | What happens                                                                                              | Output                                           |
| ---- | ---------- | --------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| 1    | Integrator | API       | Submit KYB payload to `POST /merchant-onboarding/application` with business, contact, banking, and owners | `applicationId`, `onboardingUrl`                 |
| 2    | Bead       | System    | Sends onboarding email to merchant contact using the submitted details                                    | Merchant receives secure onboarding link         |
| 3    | Merchant   | Web       | Completes missing details, uploads documents, e signs                                                     | Application ready for review                     |
| 4    | Bead       | Review    | Compliance review and decisioning                                                                         | Approval or request for more info                |
| 5    | Integrator | APIs      | After approval, provision Location then Terminal                                                          | Terminal credentials available for payment flows |

### What to send in the initial payload

* Business identity and address
* At least one contact with name and email
* Banking details for settlement
* Owner and officer details for KYB

See Reference Guide → Enumerations and Schemas → Merchant Onboarding Schema for field definitions.

### Email and link handling

* The onboarding email is sent automatically to the contact you submit
* Store `onboardingUrl` from the response so you can resend it if the merchant loses the email
* If you need to change the contact email later, submit an update through your standard process and resend the link

### Authentication and tokens

* Obtain an OAuth access token before calling the onboarding endpoint
* Cache tokens per account until expiry and refresh on demand
* Recommended pattern
  * Attempt the API call with the cached token
  * If you receive an unauthorized response, fetch a new token and retry once
  * Log the `requestId` from error responses for support

### Handling errors and duplicates

* Validation issues return a 400 with `fieldErrors` describing which fields failed
* If you see 409 Conflict for an existing application
  * Avoid re submitting the same merchant repeatedly
  * Reuse the existing application indicated by your records
* Use clear customer messaging when requesting missing documents or corrections

### Merchant approval and next steps

After approval, onboarding is complete but the merchant cannot accept payments until you:

1. Create a Location for the merchant
2. Create a Terminal for that location
3. Set the terminal webhook URL

### Operational tips

* Maintain a simple tracker for each application
  * `applicationId`, merchant legal name, primary contact, `onboardingUrl`, current status, documents outstanding
* Automate reminders
  * If an application has no progress for two days, resend the link to the merchant and notify your contact
* Keep roles clear
  * Integrator submits and tracks
  * Merchant completes and signs
  * Bead reviews and approves
* Coordinate early on banking
  * Confirm the business bank details match the legal entity name to reduce rework

### Frequently asked questions

**Can we onboard using a single integrator login for all merchants**\
Payments authenticate per merchant. For onboarding, you must use a valid OAuth token and submit each merchant individually. There is no one login to act on behalf of every merchant for payments.

**How do we re send the onboarding link**\
Use the stored `onboardingUrl` or contact support to issue a fresh link if the prior one has expired.

**When are credentials available**\
Credentials arrive with Terminal creation as part of entity management. They are not returned by the onboarding endpoint.

### Checklist

* Payload includes business, contact, banking, and owners
* OAuth token obtained and cached
* `applicationId` and `onboardingUrl` stored
* Merchant completes documents and signing
* Location created
* Terminal created and webhook set


# Using an LLM or Agent to Build a Payments POC

This page helps you quickly build a proof of concept (POC) integration using an LLM, coding agent, or IDE assistant. The fastest POC is:

1. Create a payment using `POST /Payments/crypto`
2. Present the hosted payment page URL (`paymentUrls[0]`) in a browser tab or iframe
3. Poll status using `GET /Payments/tracking/{trackingId}` until the payment reaches a final state

This approach avoids webhook setup and is ideal for demos and early prototypes.

### What you need

To run the POC, you need these values from Bead:

* `apiKey` (terminal API key)
* `merchantId`
* `terminalId`

Sandbox defaults:

* Payments base URL: `https://api.test.devs.beadpay.io`
* Payments authentication header: `X-Api-Key: <apiKey>`
* There is no separate authentication URL for Payments when using API key authentication.

### POC flow

#### Step 1: Create a hosted payment page

Endpoint:

* `POST /Payments/crypto`

Required headers:

* `X-Api-Key: {apiKey}`
* `Content-Type: application/json`

Minimal request body (recommended for POCs):

```json
{
  "merchantId": "{merchantId}",
  "terminalId": "{terminalId}",
  "requestedAmount": 1,
  "reference": "POC-ORDER-001",
  "customer": {
    "firstName": "Test",
    "lastName": "User",
    "email": "test@example.com",
    "address": "123 Main St",
    "address2": "",
    "city": "Boston",
    "state": "MA",
    "postalCode": "02110",
    "countryCode": "US"
  }
}
```

Save these values from the response:

* `paymentUrls[0]` (hosted payment page URL)
* `trackingId` (used to check status)

#### Step 2: Present the hosted payment page

You can present the hosted payment page in one of these ways:

Option A: Open in a browser tab

* Redirect or open `paymentUrls[0]` directly in the user’s browser

Option B: Embed in an iframe (web apps)

* Render an iframe with `src=paymentUrls[0]`
* Use a full height container so QR codes and tender selection are visible
* If embedding is blocked, fall back to opening in a new tab

#### Step 3: Poll for status using trackingId

Endpoint:

* `GET /Payments/tracking/{trackingId}`

Headers:

* `X-Api-Key: {apiKey}`
* `Accept: application/json`

Example curl (Sandbox):

```bash
curl -s -X GET "https://api.test.devs.beadpay.io/Payments/tracking/{trackingId}" \
  -H "X-Api-Key: {apiKey}" \
  -H "Accept: application/json"
```

Recommended polling behavior:

* Start polling after you launch the hosted page
* Poll every 2 seconds for a POC demo
* Only log or display changes when status changes
* Stop polling when `statusCode` is in a terminal state such as:
  * `completed`
  * `expired`
  * `cancelled`
  * `invalid`
  * `underpaid`
  * `overpaid`

### POC UI flow (recommended)

Build a single page with three states:

1. Amount entry

* Amount input that accepts decimals
* Primary button: Pay with crypto

2. In progress

* Show the hosted payment page (iframe or new tab link)
* Show live status (`statusCode`) while polling

3. Complete

* Show a completion view when a terminal state is reached
* Display:
  * final `statusCode`
  * requested amount
  * `trackingId`
* Button: Start a new payment (resets to amount entry)

### Common troubleshooting

401 Unauthorized

* API key missing or invalid
* Header must be exactly `X-Api-Key`
* Ensure you are using the real `apiKey` value, not a masked value

403 Forbidden

* API key is valid but not permitted for the `merchantId` or `terminalId`
* Confirm all values are from the same environment and credential set

Environment mismatch

* Sandbox values only work with the Sandbox base URL
* Production values only work with the Production base URL

### Copy and paste prompt for an LLM or coding agent

Use the prompt below in ChatGPT, an IDE agent, or another coding assistant. It produces a minimal POC that creates a payment, presents the hosted page, polls for status, and shows a completion screen.

> Prompt
>
> You are a senior full stack engineer. Build a minimal proof of concept that integrates with Bead Payments using API key authentication only.
>
> Sandbox defaults:
>
> * Payments base URL: `https://api.test.devs.beadpay.io`
> * Payments auth header: `X-Api-Key: {apiKey}`
> * No separate authentication URL is used for Payments when using API key authentication.
>
> References (use if you need to confirm schemas and endpoints):
>
> * OpenAPI (authoritative): `https://api.test.devs.beadpay.io/apidocs/v1/api.json`
> * Developer docs (supporting): `https://developers.bead.xyz/` If there is any conflict, follow the OpenAPI. For Payments authentication, do not use OAuth. Use `X-Api-Key` only.
>
> Inputs I will provide:
>
> * `{apiKey}` (terminal API key, full value)
> * `{merchantId}`
> * `{terminalId}`
>
> UI requirements:
>
> 1. Show a simple amount entry input:
>    * Label: Amount
>    * Accept decimals
>    * Validate amount is greater than 0
>    * Default to 1.00 on first load
> 2. Show a primary button:
>    * Label: Pay with crypto
> 3. When the user clicks Pay with crypto:
>    * Call the backend to create a payment with the entered amount
>    * Display the hosted payment page in an iframe (or open a new tab if iframe embedding is blocked)
>    * Display a status panel showing the current `statusCode`
> 4. When the payment reaches a terminal state, show a Payment Complete screen:
>    * Replace the iframe view with a completion view
>    * Display the final status (completed, expired, cancelled, invalid, underpaid, overpaid)
>    * Display the amount requested
>    * Display the trackingId
>    * Provide a button to Start a new payment (resets UI back to amount entry)
>
> API requirements:
>
> 1. Create a hosted payment using:
>    * `POST https://api.test.devs.beadpay.io/Payments/crypto`
>    * Header: `X-Api-Key: {apiKey}`
>    * Header: `Content-Type: application/json`
>    * JSON body includes:
>      * `merchantId: {merchantId}`
>      * `terminalId: {terminalId}`
>      * `requestedAmount: <amount from UI>`
>      * `reference: "POC-ORDER-<unique>"`
>      * `customer` object exactly as shown below
> 2. Use this customer object in the request body:
>    * `firstName: "Test"`
>    * `lastName: "User"`
>    * `email: "test@example.com"`
>    * `address: "123 Main St"`
>    * `address2: ""`
>    * `city: "Boston"`
>    * `state: "MA"`
>    * `postalCode: "02110"`
>    * `countryCode: "US"`
> 3. From the create payment response, extract:
>    * `paymentUrls[0]` as `paymentUrl`
>    * `trackingId`
> 4. Poll for status using:
>    * `GET https://api.test.devs.beadpay.io/Payments/tracking/{trackingId}`
>    * Header: `X-Api-Key: {apiKey}`
>    * Poll every 2 seconds
>    * Only log when status changes
>    * Stop polling when `statusCode` reaches a terminal state: `completed`, `expired`, `cancelled`, `invalid`, `underpaid`, `overpaid`
>
> Deliverables:
>
> * One backend endpoint (or function/route) that:
>   * accepts `requestedAmount`
>   * calls `POST /Payments/crypto`
>   * returns `paymentUrl`, `trackingId`, and echoed `requestedAmount`
> * One backend endpoint (or function/route) that:
>   * accepts `trackingId`
>   * calls `GET /Payments/tracking/{trackingId}`
>   * returns the status JSON including `statusCode`
> * One UI page with three states:
>   1. Amount Entry state (input + Pay with crypto button)
>   2. In Progress state (iframe or new tab link + live status panel)
>   3. Payment Complete state (final status + trackingId + start over button)
>
> Constraints:
>
> * Use only API key auth for Payments (`X-Api-Key`). Do not include OAuth flows, tokens, or any authentication URL.
> * Do not include webhooks.
> * Do not include onboarding or entity management.
> * Treat `apiKey` as a secret and do not log it.
> * Keep the implementation small and readable.




---

[Next Page](/llms-full.txt/1)

