> This is part 1 of 2 of the full documentation (pages 1–100 of 164).
> The content is paginated: fetch every part to see all of it.
> Next part: https://developers.bead.xyz/llms-full.txt/1
> Page index: https://developers.bead.xyz/llms.txt

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
This site has an agentic ask interface you may use to query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://developers.bead.xyz/index.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

---

# 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**
  * terminal API key (issued when a terminal is created, or by resetting the terminal's 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 the terminal API key. There is no username and password authentication for Payments.

Send:

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

The full terminal API key is returned only when a terminal is created or when its key is reset with `POST /Terminals/{id}/api-keys`. Read endpoints return a masked value only. See **Authentication** for details.

**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}
```

**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
* you have stored each terminal API key at the time it was issued, because it cannot be read back later

**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 outcome. It also includes the required behavior for overpayments and underpayments.

> **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 the payment outcome using webhooks or status polling
* Handle `overpaid` and `underpaid` outcomes correctly
* Understand the reclaim flow that returns crypto to the payer

**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`
* `GET /Payments/{paymentId}/tracking`

> `POST /Payments/crypto` is deprecated but still supported. It takes the same request body. New integrations should use `POST /Payments`.

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
```

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" \
  -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" \
  -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

`trackingId` is a legacy identifier that is still returned for backward compatibility. Do not use it in new integrations.

**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 `overpaid` or `underpaid`.

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 outcome**

You should always confirm the outcome 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, and when a reclaim return to the payer progresses.

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 an outcome.

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 the checkout once the payment leaves `created` or `processing`
* Prefer webhooks for production scale

**Step 4: Handle status outcomes**

The payment status is returned in `statusCode`.

**Pending statuses**

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

**Final statuses**

| statusCode  | Meaning                                                | What you should do                                                  |
| ----------- | ------------------------------------------------------ | ------------------------------------------------------------------- |
| `completed` | Customer paid the requested amount                     | Paid. Fulfill and mark as paid                                      |
| `overpaid`  | Customer sent more than requested                      | Paid. Fulfill and mark as paid. The excess is returned to the payer |
| `underpaid` | Customer sent less than requested                      | Not paid. Treat as declined, do not fulfill, offer a new payment    |
| `expired`   | Payment window ended without a valid completion        | Not paid. Do not fulfill                                            |
| `invalid`   | Irregular condition, funds not eligible for conversion | Not paid. Do not fulfill                                            |
| `cancelled` | Payment cancelled                                      | Not paid. Do not fulfill                                            |

These statuses do not change once reached. After an `overpaid` or `underpaid` outcome, later webhooks report the reclaim return to the payer through `refundLifecycleStatus`, with `statusCode` unchanged. Update your records, but do not change the fulfillment decision. See [Payment Statuses](/payments/payment-statuses).

**Overpayments and underpayments**

Overpayments and underpayments occur because the payer manually enters the crypto amount in their wallet app.

**Overpaid**

Definition: the payer sent more than the requested amount.

Integrator behavior:

* Treat as a paid payment
* Fulfill the order
* Do not ask the customer to pay again

What Bead does:

* The requested amount settles to the merchant
* The excess is returned to the payer through the reclaim process
* `statusCode` stays `overpaid`. When the excess has been returned, `refundLifecycleStatus` is `completed`

**Underpaid**

Definition: the payer sent less than the requested amount.

Integrator behavior:

* Treat as a declined payment
* Do not fulfill the order
* If the customer still wants to pay, create a new payment and start a new hosted checkout session, with the same tender or a different one

What Bead does:

* Nothing settles to the merchant
* The full amount received is returned to the payer through the reclaim process
* `statusCode` stays `underpaid`. When the amount has been returned, `refundLifecycleStatus` is `completed`

**Reclaim**

Reclaim is how Bead returns crypto to the payer that is not part of a paid amount.

Reclaim can occur when the payment is:

* `overpaid` (the excess only)
* `underpaid`
* `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 overpaid and underpaid 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 leaves created or processing**

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.

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.

{% hint style="danger" %}
**Venmo cannot currently be tested end to end.** Venmo has no sandbox, and testing with a live account fails on Venmo's side after verification. See [Test with Venmo](/testing/test-with-venmo) for details. This does not affect Klarna, PayPal, or crypto testing.
{% endhint %}

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      |
| **PayPal**                        | Virtual only         | No. Uses a sandbox PayPal test account                             | Validating the wallet approval flow on a virtual terminal                          |
| **Venmo**                         | Virtual only         | No sandbox available                                               | Not currently testable end to end. See [Test with Venmo](/testing/test-with-venmo) |
| **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 **PayPal**, which completes in Sandbox without drawing from a live account. **Venmo cannot be tested end to end at this time** — see [Test with Venmo](/testing/test-with-venmo) before planning around it.

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 PayPal** — virtual terminal wallet flow
* **Test with Venmo** — current status: no test path available
* **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#choosing-a-test-method).

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` to generate the hosted checkout URL.

{% hint style="info" %}
`POST /Payments/crypto` is deprecated but still supported. It accepts the same request body as `POST /Payments`. Use `POST /Payments` for new work and move existing calls when you can.
{% endhint %}

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" \
-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" \
-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 with `statusCode` set to `completed`.

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

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

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 `statusCode` set to `completed`.
* 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 the wallet approval flow with [Test with PayPal](/testing/test-with-paypal). Venmo cannot currently be validated end to end. See [Test with Venmo](/testing/test-with-venmo) for current status.

#### Related

* [Choosing a Test Method](/testing#choosing-a-test-method)
* [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

{% hint style="danger" %}
**There is no testing environment currently available for Venmo.** Venmo does not provide a sandbox, and attempting to test with a live account will fail. Venmo integration cannot be validated end-to-end at this time.
{% endhint %}

Venmo does not offer a sandbox or test credential set. Earlier guidance on this page suggested that a live, funded Venmo account could be used as a workaround for end-to-end testing. That workaround has been re-tested and confirmed **not to work**: attempting a transaction with a live Venmo account now throws an error on Venmo's side after phone or email verification completes, and the transaction does not go through.

As of now, there is no functioning way to test Venmo end-to-end, in Sandbox or with a live account.

#### What this means for your integration

* You cannot currently validate the Venmo wallet redirect, QR fallback, or completion flow before going live.
* Venmo can still be configured as a `tenderTypes` value (`venmo`) on a terminal with `type` = `virtual`, and the integration will build and deploy normally. What's blocked is **end-to-end verification** of the checkout flow itself.
* For a virtual terminal, use [Test with Klarna (recommended)](/testing/test-with-klarna-recommended) or [Test with PayPal](/testing/test-with-paypal) to validate the parts of your integration that are shared across tenders — payment creation, hosted checkout launch, webhook delivery, and status checks. Those confirm your API keys, terminal configuration, and webhook handling all work; they do not substitute for a completed Venmo transaction.
* If Venmo is a required tender for your launch, contact developer support for the current status before committing to a go-live date.

#### Related

* [Testing](/testing)
* [Test with Klarna (recommended)](/testing/test-with-klarna-recommended)
* [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#choosing-a-test-method).

### 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` to generate the hosted checkout URL. On a terminal with `type` = `virtual`, include `refundEmail` and a `customer` object.

{% hint style="info" %}
`POST /Payments/crypto` is deprecated but still supported. It accepts the same request body as `POST /Payments`. Use `POST /Payments` for new work and move existing calls when you can.
{% endhint %}

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" \
-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 with `statusCode` set to `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 `statusCode` set to `completed`.
* 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#choosing-a-test-method)
* [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#choosing-a-test-method).

### 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` 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" \
  -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 overpaid, underpaid, expired, invalid, or cancelled, Bead returns crypto to the payer through the reclaim process and emails reclaim instructions to that address. For an overpaid payment, only the excess is returned.

> `POST /Payments/crypto` is deprecated but still supported. It takes the same request body. New integrations should use `POST /Payments`.

### 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 `overpaid` or `underpaid`. 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 with the outcome.

**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 when it reaches `completed` or `overpaid`. Treat `underpaid`, `expired`, `invalid`, and `cancelled` as not paid. See [Payment Statuses](/payments/payment-statuses).

### 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#choosing-a-test-method)
* [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 a terminal API key sent in the `X-Api-Key` header
* **Onboarding API** uses a partner 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

There is no username and password authentication for Payments. All terminal payment requests authenticate with the terminal API key in the `X-Api-Key` header.

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 the terminal API key in the `X-Api-Key` header
* **Onboarding API**: use your partner API key in the `X-Api-Key` header
* **Other Bead APIs**: follow the authentication requirements documented for that API family or endpoint

### Payments authentication: terminal API key

A terminal API key is the secret credential used to authenticate Payments requests for a terminal. 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" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: {apiKey}" \
  -d '{
    "merchantId": "{merchantId}",
    "terminalId": "{terminalId}",
    "requestedAmount": 25.00,
    "reference": "ORDER123"
  }'
```

`POST /Payments/crypto` is deprecated but still supported, and uses the same `X-Api-Key` authentication. Use `POST /Payments` for new work.

#### How to obtain a terminal API key

The full terminal API key is returned only by the calls that create or issue it:

* `POST /Terminals` returns the full key in `apiKey.secret` when the terminal is created.
* `POST /Terminals/{id}/api-keys` issues a new key and returns it in `apiKey.secret`. This is the only way to obtain a key for a terminal that already exists, including the default terminals created when a merchant is boarded.

`GET /Terminals/{id}` and `GET /Terminals` never return a usable key to an integration. They return only the masked value in `apiKey.maskedSecret`, and `apiKey.secret` is empty.

Resetting a key invalidates the previous key immediately, so update every system that uses the terminal before or as you reset it. Store the new key as soon as you receive it. See [Reset Terminal API Key](/entity-management/terminal-management/reset-terminal-api-key).

#### 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, reset it immediately with `POST /Terminals/{id}/api-keys`

#### Payments troubleshooting

* `401 Unauthorized`: the API key is missing, invalid, masked, replaced by a reset, 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`, 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 a partner 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.

### OAuth token endpoint (OpenID Connect)

OAuth access tokens are used by Bead APIs and endpoints that require OAuth authentication. Payments does not use OAuth.

#### 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, not the masked value
* Confirm the key matches the correct environment
* Confirm the key was not replaced by a later reset
* 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](/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, including reclaim returns to the payer.

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`
* Response: `paymentId`, `trackingId`, `paymentPageId`, `paymentUrls`, `unavailableTenders`

`POST /Payments/crypto` is deprecated but still supported, and takes the same request body. New integrations should use `POST /Payments`.

Go to [Create Payment](/payments/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. For in-person checkout on a physical terminal, see [In-Person Payments (Physical Terminals)](/reference-guide/payment-flows/in-person-payments-physical-terminals).

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 the outcome**

Confirm the payment outcome before fulfilling an order. `completed` and `overpaid` are paid. `underpaid`, `expired`, `invalid`, `cancelled`, and `abandoned` are not paid.

You have two options:

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

Go to [Payment Statuses](/payments/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](/faqs-and-troubleshooting/environment-and-testing/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 the outcome. 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](/faqs-and-troubleshooting/payments-faqs/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` 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.

> **`POST /Payments/crypto` is deprecated.** It is still supported and takes the same request body, so existing integrations keep working. New integrations should use `POST /Payments`, and existing integrations should plan to move to it. `POST /Payments` returns every rejection in the standard problem details format.

**Overview**

Endpoint:

`POST /Payments`

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](/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`, `paymentPageId`, `paymentUrls`, and `unavailableTenders`.

Endpoint:

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

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`
* `splitFunding`, the split funding block, only for merchants enrolled in the [Split Funding Program](/split-funding)

Required fields can vary by tender type, terminal configuration, and risk or compliance requirements. `customer` and `refundEmail` are both required for virtual terminals, see below. Use the simplest request body that matches your flow, and add optional fields only when your terminal type and tender flow require 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. For guidance on building an in-person checkout, see [In-Person Payments (Physical Terminals)](/reference-guide/payment-flows/in-person-payments-physical-terminals).

**Terminal type = virtual**

For virtual terminals, both the `customer` object and `refundEmail` are required. Include the full set of required customer fields, and a valid `refundEmail`, on every virtual terminal payment request. A virtual terminal request that omits `refundEmail` returns `400 Bad Request`, even if `customer` is present and complete. `refundEmail` must be sent explicitly and is not inferred from `customer.email` on virtual terminals.

**Terminal type = physical**

For physical terminals, the request can generally use the basic payment fields without a `customer` object or `refundEmail`. Physical terminal flows are typically customer-present and do not need `customer` details in the common case. Integrators commonly omit `customer` entirely for physical terminal transactions.

If your specific tender flow or terminal configuration does require customer details on a physical terminal, include the complete `customer` object.

`refundEmail` is optional on physical terminals. If you omit it but include a `customer` object with `email`, Bead uses `customer.email` for reclaim instead. See [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto) for the full behavior.

**Customer object rule**

Do not send a partial `customer` object.

If you include `customer` (required on virtual, optional on physical unless your flow requires it), 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, which 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.

**Tenders left off the hosted page**

A tender you asked for is not always a tender the consumer gets. The response carries an `unavailableTenders` array listing each tender that was excluded from the hosted page, with the reason. An empty array means every eligible tender is on the page.

| Field             | Meaning                                                                                         |
| ----------------- | ----------------------------------------------------------------------------------------------- |
| `tenderType`      | The tender that was left off                                                                    |
| `reason`          | `feeNotCovered` or `noContractedRate`                                                           |
| `fee`             | Bead's fee for that tender on this payment. Null when there is no contracted rate to compute it |
| `transactionRate` | The contracted tender rate. Null when there is no contracted rate                               |
| `perItemFee`      | The contracted per-item fee. Null when there is no contracted rate                              |

`feeNotCovered` means the withheld partner portion was smaller than Bead's fee for that tender, so the tender was removed rather than settling the merchant short. `noContractedRate` means your partner fee schedule has no rate row for that tender. **A missing contracted rate excludes the tender.** It is not treated as free.

Check `unavailableTenders` on every split funding payment. It is the only signal at creation time that the consumer will see fewer ways to pay than you intended.

**Split funding payments**

If the merchant is enrolled in the [Split Funding Program](/split-funding), add an optional `splitFunding` object to specify exactly how much of the payment the merchant keeps and how much is withheld for the partner. This is unrelated to tender selection and can be combined with any tender flow. Omit the object for a standard payment. Enrolled merchants can send both kinds and they settle side by side.

**The object was previously named `rebate`. That name is no longer accepted.** There was no dual-accept transition window: as of the current release, a request that sends `rebate` gets `200 OK` with the payment created as an ordinary payment. The field is silently dropped, nothing is withheld for the partner, and no error is returned. If your integration still sends `rebate`, switch it to `splitFunding` before relying on partner withholding, and confirm the change against a real split funding transaction report, since a successful response alone will not tell you the field was ignored.

Four things to know before you build against it:

* **The amounts must balance to the cent.** `merchantPortion + partnerPortion` must exactly equal `total`, and `total` must equal `requestedAmount`.
* **Bead's fee is charged on `total`.** The contracted tender rate applies to the full amount the consumer paid, not to the merchant portion, plus the per-item fee. The whole fee comes out of the partner portion, so size the partner portion against the consumer total rather than against the merchant's price.
* **The destinations are fixed.** The merchant portion funds the merchant's account on file and the partner portion funds your partner account on file. There is no way to name any other payee on the payment.
* **The program carries obligations.** It is normally operated with a consumer fee that you size, disclose and present yourself, per tender. Read [What this program requires of you](/split-funding#what-this-program-requires-of-you) before you scope the work.

**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 outcome
* fulfill only after the payment reaches `completed` or `overpaid`

**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 for a physical terminal.

```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 required for virtual terminals.

```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",
    "city": "Chicago",
    "state": "IL",
    "postalCode": "60601",
    "countryCode": "US"
  }
}
```

**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 for virtual terminals (where `customer` and `refundEmail` are both required), or for a physical terminal flow that specifically requires customer details.

```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` or `overpaid`.

**Example request body: split funding**

Use the split funding block only for a merchant enrolled in the [Split Funding Program](/split-funding).

```json
{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 100.00,
  "reference": "ORDER-4821",
  "splitFunding": {
    "total": 100.00,
    "merchantPortion": 90.00,
    "partnerPortion": 10.00
  }
}
```

**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` and `refundEmail` are both required for virtual terminals. For physical terminals, omit `customer` unless your selected tender, terminal configuration, or compliance flow requires it, and `refundEmail` is optional.
* If you include `customer`, provide all required customer fields.
* `refundEmail` is used to send reclaim instructions if the payment ends as `overpaid`, `underpaid`, `expired`, `invalid`, or `cancelled`. On physical terminals where it is omitted, `customer.email` is used instead if present.
* In Sandbox, keep normal happy-path test amounts at or below `100.00`.

**Example response**

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

**Example response: a split funding payment with tenders left off**

The rate values below are illustrative placeholders. Your own contracted rates are the ones returned.

```json
{
  "paymentId": "pay_c10b29e3c4b54d8aa12f9934",
  "trackingId": "c10b29e3c4b54d8aa12f9934",
  "paymentPageId": "6539fa89f0363f1722b377ef",
  "paymentUrls": [
    "https://pay.test.devs.beadpay.io/6539fa89f0363f1722b377ef"
  ],
  "unavailableTenders": [
    {
      "tenderType": "klarna",
      "reason": "feeNotCovered",
      "fee": 4.20,
      "transactionRate": 0.04,
      "perItemFee": 0.20
    },
    {
      "tenderType": "cashApp",
      "reason": "noContractedRate",
      "fee": null,
      "transactionRate": null,
      "perItemFee": null
    }
  ]
}
```

**Key fields**

`terminalId` is the terminal identifier in Bead. It 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` is the merchant identifier. Used for reporting and settlement.

`requestedAmount` is the 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` is an 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` is an 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` or `overpaid`. When you limit a payment to USDC on Base or USDC on Solana, the `requestedAmount` must be at least `1.00`.

`reference` is a merchant-side reference such as an order or invoice number.

`description` is a description that may be shown to the customer and in reporting.

`customer` is required for virtual terminals. It is optional for physical terminals unless your selected tender, terminal configuration, compliance flow, or reporting process requires customer details. If included, provide the full required customer fields.

`redirectUrl` is an optional URL Bead redirects to after checkout completes or is cancelled.

`emailReceipt` and `smsReceipt` are optional receipt delivery settings.

`refundEmail` is required for virtual terminals. It is optional for physical terminals, where `customer.email` is used automatically if `refundEmail` is omitted. It is used for reclaim instructions when crypto must be returned to the payer.

`webhookUrls` is an optional array of additional webhook endpoints for this payment.

`splitFunding` is an optional split funding object. It is only accepted for merchants enrolled in the [Split Funding Program](/split-funding) and carries `total`, `merchantPortion`, and `partnerPortion`. Omit entirely for a standard payment. Bead's fee is calculated on `total`, the full amount the consumer paid, and taken from the partner portion. **The earlier name `rebate` is no longer accepted.** Sending it is not rejected: it is silently dropped, the request still returns `200 OK`, and the payment is created as an ordinary payment with nothing withheld for the partner. There is no way to detect this from the response. Confirm your integration sends `splitFunding` before relying on partner withholding.

`paymentPageId` is returned on every payment. It identifies the hosted payment page and is the trailing segment of the URL in `paymentUrls`.

`unavailableTenders` is returned on every payment. It lists the tenders that were excluded from the hosted page, each with `tenderType`, `reason`, `fee`, `transactionRate`, and `perItemFee`. On a standard payment it is normally empty. On a split funding payment, check it before you present the hosted page.

**Overpayments and underpayments**

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:

`overpaid`, the customer sent more than the requested amount.

`underpaid`, the customer sent less than the requested amount.

Both are final statuses. Recommended handling:

* Overpaid: treat as paid and fulfill the order. The requested amount settles to the merchant. The excess is returned to the payer through the reclaim flow. `statusCode` stays `overpaid`, and reclaim progress is reported in `refundLifecycleStatus`.
* Underpaid: treat as declined. Do not fulfill. The crypto sent by the customer is returned through the reclaim flow. `statusCode` stays `underpaid`, and reclaim progress is reported in `refundLifecycleStatus`. If the customer still wants to pay, create a new payment and start a new hosted checkout.

See [Under and Over Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling).

**Email behavior for reclaim**

`refundEmail` is required on virtual terminals, so an email is always on file for reclaim on those payments.

On physical terminals, if you provide `refundEmail`, Bead emails reclaim instructions when reclaim is required. If you omit it but include a `customer` object with `email`, Bead uses `customer.email` instead. If neither is available, the hosted payment page prompts the payer to enter an email address when an overpaid or underpaid outcome occurs, then Bead emails reclaim instructions.

See [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto) for full details.

**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](/payments/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 the outcome.

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

Split funding payments do not change webhook behavior. Payloads are unchanged, and no event is emitted for a partner distribution.

**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 outcome 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 an outcome.

**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, and whenever a reclaim return to the payer progresses
* 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 outcome is not known yet
* `completed`, the payment is paid. Use this as a fulfillment trigger
* `overpaid`, the payment is paid and the excess is being returned to the payer. Use this as a fulfillment trigger
* `underpaid`, `expired`, `invalid`, or `cancelled`, the payment is not paid and should not be fulfilled

Every status after `created` and `processing` is final. Reclaim progress is reported in `refundLifecycleStatus` and does not change `statusCode`.

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 the outcome.

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

Note for split funding payments: the amounts you sent are not returned on this tracking response. See [What is not returned](/split-funding#what-is-not-returned).

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 your terminal type is `virtual`, confirm you are sending both a complete `customer` object and `refundEmail`. Both are required. A missing `refundEmail` returns `400 Bad Request` on virtual terminals even if `customer` is complete.
* 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` on a physical terminal, omit the object instead of sending a partial object.
* If using `refundEmail`, confirm it is a valid email address, not an empty string.
* If sending a split funding block, confirm the merchant is enrolled in the Split Funding Program, confirm you are sending `splitFunding` and not the retired `rebate` field, and confirm `merchantPortion + partnerPortion` exactly equals `total`. See [Why a split funding payment is rejected](/split-funding#why-a-split-funding-payment-is-rejected).

**Payment succeeds but the partner portion was not withheld**

* This is not a rejection, so it will not surface as an error. The most common cause is an integration still sending `rebate` instead of `splitFunding`; that field is silently dropped rather than rejected.
* Confirm the request body uses `splitFunding`, not `rebate`.
* Confirm against a Split Funding Reports transaction row for the payment: a genuine split funding payment shows a `partnerPortion` greater than zero. A `0` or missing `partnerPortion` confirms the field was not recognized.

**Tenders are missing from the hosted page on a split funding payment**

* Read `unavailableTenders` on the create payment response. Each entry names the tender and the reason it was excluded.
* `feeNotCovered` means the partner portion did not cover Bead's fee for that tender. Bead's fee is the contracted tender rate on `total` plus the per-item fee, so a partner portion sized against the merchant portion can fall short. Increase the partner portion, or narrow `tenderTypes` to the tenders your fee covers.
* `noContractedRate` means your partner fee schedule has no rate for that tender. That is a configuration gap, not a sizing problem. Raise it with your Bead contact rather than adjusting the amounts.
* See [Coverage is checked when the payment is created](/split-funding#coverage-is-checked-when-the-payment-is-created).

**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 not paid outcome 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
* If the merchant is on the Split Funding Program, see [Split Funding](/split-funding) for the additional fields, the validation rules and the obligations it carries
* 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` 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

{% hint style="info" %}
The API Reference also lists `POST /Payments/crypto`. That endpoint is deprecated but still supported and accepts the same request body. Use `POST /Payments` for new work.
{% endhint %}

#### 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
```

Find the `POST /Payments` endpoint.

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

![](https://450383315-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjL0BeBYhFGcs8tIiuCcV%2Fuploads%2Fcy5UsJ8bkaVs8lMy7CHD%2Fimage.png?alt=media\&token=bb5da557-201e-4f31-bb8b-0f9c88826826)

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="https://450383315-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjL0BeBYhFGcs8tIiuCcV%2Fuploads%2FRHXYtEa2XdBNDSz7fCcV%2Fimage.png?alt=media&#x26;token=cc62328b-0a6f-44d1-bb28-61c927604136" 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="https://450383315-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjL0BeBYhFGcs8tIiuCcV%2Fuploads%2FwA2kWI8y4rAcV5WGA23l%2Fimage.png?alt=media&#x26;token=566355b7-0ef6-46db-bf30-4eb8adff638f" 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

#### Step 5: Send the request

<figure><img src="https://450383315-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjL0BeBYhFGcs8tIiuCcV%2Fuploads%2FoYudHioyupR9mBYZTeRq%2Fimage.png?alt=media&#x26;token=68458610-ee13-4649-916b-8da8346838bc" alt=""><figcaption></figcaption></figure>

Select `Send`.

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

The response includes:

* `paymentId`
* `trackingId` (legacy identifier, still returned for backward compatibility; do not use it in new integrations)
* `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.

{% hint style="warning" %}
**`overpaid` and `underpaid` are final statuses.** An `overpaid` payment is a paid payment: fulfill the order. The requested amount settles to the merchant and only the excess is returned to the payer. An `underpaid` payment is not paid: do not fulfill, and offer the customer a new payment. Reclaim progress is reported in `refundLifecycleStatus` and does not change `statusCode`. See "Final statuses" below.
{% endhint %}

**Overview**

Use the payment status endpoint when you want to:

* retrieve near real time payment status with a simple GET request
* confirm the outcome of a payment, such as `completed`, `overpaid`, `underpaid`, `expired`, `abandoned`, or `invalid`
* confirm that a reclaim return has completed, shown by `refundLifecycleStatus` set to `completed`
* 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 tracking endpoint returns the same payload shape as a payment webhook. It includes the reclaim fields (`refundLifecycleStatus`, `refundInitiatedAt`, `refundCompletedAt`, `refundFailureReason`). It does not include settlement or batch identity (`settlementId`, `batchId`, `batchPeriod`, `batchStatus`). See "Settlement and batch identity" below for where to get those.

The example below shows an `overpaid` payment whose payer has been emailed reclaim instructions for the excess. `tenderType` identifies the tender actually used to pay.

```json
{
  "paymentId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
  "trackingId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
  "paymentCode": "PAY-9X7K2M",
  "statusCode": "overpaid",
  "tenderType": "usdcBase",
  "amounts": {
    "requested": {
      "inPaymentCurrency": {
        "amount": 100,
        "currency": { "id": 16, "code": "USDC_BASE", "name": "USDC Base", "symbol": "USDC" }
      },
      "inRequestedCurrency": {
        "amount": 100,
        "currency": { "id": 1, "code": "USD", "name": "USD", "symbol": "$" }
      }
    },
    "paid": {
      "inPaymentCurrency": {
        "amount": 105,
        "currency": { "id": 16, "code": "USDC_BASE", "name": "USDC Base", "symbol": "USDC" }
      }
    }
  },
  "reference": "ORDER123",
  "description": null,
  "receivedTime": "2026-10-06T13:55:02.114Z",
  "terminalId": "65f0b274f9a4c9a3f8d4b2a1",
  "merchantId": "664c5e3b0517b0a8a6321c9a",
  "errorMessage": null,
  "originalSenderAddress": "0x8f3c...a91d",
  "conversions": [],
  "refundLifecycleStatus": "emailSent",
  "refundInitiatedAt": "2026-10-06T13:57:20.207Z",
  "refundCompletedAt": null,
  "refundFailureReason": null
}
```

**Settlement and batch identity**

This endpoint is for payment status only. For settlement and batch identity on a payment, use the Reporting payment history endpoints instead:

| Endpoint                       | Settlement/batch fields returned                            |
| ------------------------------ | ----------------------------------------------------------- |
| `GET /Partners/{id}/payments`  | `settlementId`, `batchId`, `batchPeriod`, `batchStatus`     |
| `GET /Merchants/{id}/payments` | `settlementId`, `batchId`, `batchPeriod`, `batchStatus`     |
| `GET /Terminals/{id}/payments` | `batchId`, `settlementId`, `batchStatus` (no `batchPeriod`) |

These fields populate as the payment moves through batching and settlement, so expect them to be null until a payment has been batched (and, for `settlementId`, until the batch has rolled into a settlement). `completed` and `overpaid` payments are batched. See [Reporting](/reporting) for these endpoints and [Settlement](/settlement) for how settlement and batch records work.

**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 paid 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` or `overpaid`, not `processing`, as the fulfillment trigger

**Status values**

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

`partiallyRefunded` and `fullyRefunded` are reserved values. Reclaim progress and completion are reported in `refundLifecycleStatus`, not in `statusCode`. See "Reclaim progress and completion" below.

**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:

* `overpaid` when more than the requested amount is sent. The payment is paid. The requested amount settles to the merchant, and the excess is returned to the payer through Bead's reclaim process.
* `underpaid` when less than the requested amount is sent. The payment is not paid. Nothing settles to the merchant, and the full amount sent is returned to the payer through reclaim.

A very small overpayment, within a per tender tolerance set by Bead, is classified as `completed` instead of `overpaid`, and no reclaim is offered for the excess.

**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 its outcome is not known yet. 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. Paid: fulfill the order. Final status.                                                                                                                                                                                     |
| `overpaid`          | Customer sent more than requested. Paid: fulfill the order. The requested amount settles to the merchant. The excess is returned to the payer through reclaim. Final status.                                                                                                                                                                                      |
| `underpaid`         | Customer sent less than requested. Not paid: do not fulfill. Treat it like a declined payment and offer the customer a new payment. The amount sent is returned to the payer through reclaim. Final status.                                                                                                                                                       |
| `expired`           | A payment attempt was started, but no funds were received before the payment window expired. Any funds sent later remain unconverted and must be reclaimed. When Bead closes a payment as `expired` automatically after the session lifetime, this is not currently delivered as a webhook event. See "Automatic closures and webhooks" below. Final status.      |
| `abandoned`         | The payment was created, but the customer never started a payment attempt, so no quote, deposit address, or checkout was ever issued. Bead closes these payments automatically after the hosted payment session lifetime has passed. Final status. See "Abandoned payments" below.                                                                                |
| `invalid`           | Irregular event such as multiple transactions, unsupported asset, compliance block, or extreme market movement. Funds remain unconverted and must be reclaimed. Final status.                                                                                                                                                                                     |
| `cancelled`         | Customer or merchant cancelled the payment. Any funds remain unconverted and may be reclaimed. Final status.                                                                                                                                                                                                                                                      |
| `partiallyRefunded` | Reserved. Not returned.                                                                                                                                                                                                                                                                                                                                           |
| `fullyRefunded`     | Reserved. Not returned.                                                                                                                                                                                                                                                                                                                                           |

**Abandoned payments**

A payment is `abandoned` when it was created but the customer never began paying. For example, your system created a payment and the customer never opened the hosted payment page, or opened it and left without choosing a tender or starting a checkout. No funds were requested or expected, so there is nothing to reclaim.

How this differs from `expired`:

* `abandoned`: no payment attempt was recorded for the payment. Nothing was quoted or issued to the customer.
* `expired`: a payment attempt was started (for example a crypto quote, deposit address, or hosted checkout was issued) but was not completed before its window closed.

Because a payment may sit in `created` for the length of the hosted payment session before Bead closes it, `created` is not a permanent state for payments that are never paid. Expect these payments to move to `abandoned` after the session lifetime has passed.

**Automatic closures and webhooks**

> **Webhook behavior:** when Bead closes a payment automatically after the hosted payment session lifetime, neither `abandoned` nor `expired` is currently delivered as a payment webhook event. Both are available from `GET /Payments/{paymentId}/tracking`. A payment can therefore remain in `created` or `processing` in your system after Bead has closed it. If your system marks an order as pending when a payment is created, do not wait for a webhook to close it out. Use your own timeout and reconcile with the tracking endpoint. See [Payment Webhooks](/payments/payment-webhooks).

**Reclaim progress and completion**

When a payment ends as `overpaid`, `underpaid`, `expired`, `invalid`, or `cancelled`, Bead returns crypto to the payer through the reclaim process. For `overpaid`, only the excess is returned. For the other statuses, the full amount received is returned. The progress of that return is on the payment record:

| Field                   | Meaning                                                                                                                                                 |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `refundLifecycleStatus` | One of `notStarted`, `emailSent`, `customerClaimed`, `processing`, `completed`, `failed`, `rejected`. A value of `null` means the same as `notStarted`. |
| `refundInitiatedAt`     | The first time Bead emailed the payer reclaim instructions or the payer started a claim, whichever came first.                                          |
| `refundCompletedAt`     | When Bead confirmed the return to the payer.                                                                                                            |
| `refundFailureReason`   | Populated when a return attempt failed.                                                                                                                 |

When the return completes, `refundLifecycleStatus` becomes `completed` and `refundCompletedAt` is set. `statusCode` does not change: an `overpaid` payment stays `overpaid`, and an `underpaid` payment stays `underpaid`.

These fields are returned by `GET /Payments/{paymentId}/tracking`, `GET /Payments/{paymentId}`, payment webhooks, and the merchant and partner payment history endpoints.

> **Webhook behavior:** Bead sends a payment webhook each time `refundLifecycleStatus` changes, including the change to `completed`. The `statusCode` in these webhooks is the payment's final status, unchanged. The webhook goes to the terminal webhook URL and to any `webhookUrls` set on the payment, using the same payload as other payment webhooks.

See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status) for lifecycle values, webhook handling, and example responses.

**Final statuses**

`created` and `processing` are pending. Every other status is final: once a payment reaches it, `statusCode` does not change.

| Status       | Outcome  | Fulfillment    | Reclaim possible          |
| ------------ | -------- | -------------- | ------------------------- |
| `created`    | Pending  | Wait           | No                        |
| `processing` | Pending  | Wait           | No                        |
| `completed`  | Paid     | Fulfill        | No                        |
| `overpaid`   | Paid     | Fulfill        | Yes, the excess only      |
| `underpaid`  | Not paid | Do not fulfill | Yes, the full amount sent |
| `expired`    | Not paid | Do not fulfill | Yes, if funds arrived     |
| `invalid`    | Not paid | Do not fulfill | Yes, if funds arrived     |
| `cancelled`  | Not paid | Do not fulfill | Yes, if funds arrived     |
| `abandoned`  | Not paid | Do not fulfill | No                        |

Rules for your webhook consumer:

* Act on the first final status you receive for a `paymentId`. A payment never returns to `created` or `processing`.
* Expect later webhooks while a reclaim progresses. They carry the same final `statusCode` with an updated `refundLifecycleStatus`. Update your reclaim records, but do not reverse the fulfillment decision.
* A return may never happen. If the payer never claims, or the amount is below the minimum that can be returned, `refundLifecycleStatus` does not reach `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 is paid.

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 outcome 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 the outcome.

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 outcome
* fulfill only after the payment reaches `completed` or `overpaid`

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 a paid payment.
* Store the returned `tenderType` as the tender actually paid.
* Proceed with fulfillment after you have confirmed the outcome via webhooks or this endpoint.
* Use `paymentId`, `statusCode`, `tenderType`, `reference`, and `receivedTime` to support order lookup and customer support workflows.

**`overpaid`**

* Treat as a paid payment and fulfill the order.
* The requested amount settles to the merchant like a `completed` payment.
* The excess is returned to the payer through Bead's reclaim process. Bead emails the payer reclaim instructions when an email is on file.
* Do not ask the customer to pay again.
* Expect later webhooks as the return of the excess progresses, with `statusCode` still `overpaid` and `refundLifecycleStatus` updated. Keep the order fulfilled.

**`underpaid`**

* Treat as a declined payment. Nothing was processed for the order.
* Do not fulfill the order.
* Offer the customer a new payment, using your normal retry or tender selection flow. The original payment session cannot be topped up.
* The amount sent is returned to the payer through Bead's reclaim process.
* Expect later webhooks as the return progresses, with `statusCode` still `underpaid` and `refundLifecycleStatus` updated.

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

* Treat as not paid.
* Do not fulfill the order.
* If funds were sent (not applicable to `abandoned`), they remain unconverted and are handled through reclaim.
* For `abandoned`, no funds were requested. If the customer still wants to pay, create a new payment.

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

**Reclaiming crypto**

When a payment ends in one of the following states:

* `overpaid` (the excess only)
* `underpaid`
* `expired`
* `invalid`
* `cancelled`

crypto associated with that payment is returned to the payer through Bead's reclaim process.

**Email capture behavior**

Bead uses an email based reclaim flow to return 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 after a not paid outcome

**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, including reclaim progress. Use this endpoint for ad hoc checks and support workflows, not as your primary real time mechanism. Because payments that Bead closes automatically (`abandoned` and `expired`) are not currently sent by webhook, also reconcile any payment still open after your own timeout with this endpoint.

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 the outcome.

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 the outcome is known

**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` once the payment has an outcome. 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 every status into an internal state that your business and support team understand. `completed` and `overpaid` are paid. `underpaid`, `expired`, `abandoned`, `invalid`, and `cancelled` are not paid. Track reclaim progress separately, using `refundLifecycleStatus`.

**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  | Outcome  | Internal state                                 | Fulfillment behavior    |
| ------------ | -------- | ---------------------------------------------- | ----------------------- |
| `created`    | Pending  | Awaiting payment                               | Do not fulfill yet      |
| `processing` | Pending  | Payment detected / pending confirmation        | Do not fulfill yet      |
| `completed`  | Paid     | Paid                                           | Fulfillment may proceed |
| `overpaid`   | Paid     | Paid, excess returned to payer through reclaim | Fulfillment may proceed |
| `underpaid`  | Not paid | Payment declined / retry with a new payment    | Do not fulfill          |
| `expired`    | Not paid | Payment expired                                | Do not fulfill          |
| `abandoned`  | Not paid | Payment not started / closed                   | Do not fulfill          |
| `invalid`    | Not paid | Payment invalid / review required              | Do not fulfill          |
| `cancelled`  | Not paid | 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 [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status) for how to follow a reclaim return through to completion.
* 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 overpaid, underpaid, and reclaim outcomes.
* Review [Why is my payment still Created or In Progress?](/faqs-and-troubleshooting/payments-faqs/why-is-my-payment-still-created-or-in-progress) for what to expect when a payment is started but not finished.
* 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/{paymentId}/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, reclaim progress, and optional merchant-supplied metadata.

```json
{
  "paymentId": "string",
  "trackingId": "string",
  "paymentCode": "string",
  "statusCode": "created | processing | completed | overpaid | underpaid | expired | abandoned | invalid | cancelled",
  "tenderType": "string",
  "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,
  "originalSenderAddress": null,
  "conversions": [],
  "refundLifecycleStatus": "notStarted | emailSent | customerClaimed | processing | completed | failed | rejected",
  "refundInitiatedAt": null,
  "refundCompletedAt": null,
  "refundFailureReason": null
}
```

### Field notes

| Field                                   | Description                                                                                                                                                                              |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `paymentId`                             | Bead payment identifier returned when the payment was created. Use it as the primary identifier for the whole payment lifecycle, including `GET /Payments/{paymentId}/tracking`.         |
| `trackingId`                            | Legacy identifier, deprecated but still returned for backward compatibility. Do not use it for new integrations.                                                                         |
| `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.                                                                                                          |
| `tenderType`                            | The tender actually used to pay, once known.                                                                                                                                             |
| `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.                                                                                                       |
| `originalSenderAddress`                 | The sending wallet address for crypto payments, when available.                                                                                                                          |
| `conversions`                           | Currency conversion details, when applicable.                                                                                                                                            |
| `refundLifecycleStatus`                 | Where a reclaim return to the payer stands. A value of `null` means the same as `notStarted`. See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status). |
| `refundInitiatedAt`                     | When Bead first emailed the payer reclaim instructions or the payer started a claim.                                                                                                     |
| `refundCompletedAt`                     | When Bead confirmed the return to the payer.                                                                                                                                             |
| `refundFailureReason`                   | Populated when a return attempt failed.                                                                                                                                                  |

### Status examples covered

| Status       | Outcome  | What the example shows                                                                                              |
| ------------ | -------- | ------------------------------------------------------------------------------------------------------------------- |
| `created`    | Pending  | Payment record created; no funds confirmed yet                                                                      |
| `processing` | Pending  | Payment is in progress; the outcome is not yet known                                                                |
| `completed`  | Paid     | Customer paid the requested amount. Final status                                                                    |
| `overpaid`   | Paid     | Customer sent more than the requested amount. The requested amount settles and the excess is returned. Final status |
| `underpaid`  | Not paid | Customer sent less than the requested amount. Treat as declined. Final status                                       |
| `expired`    | Not paid | Payment expired before completion. Final status                                                                     |
| `cancelled`  | Not paid | Payment was cancelled before completion. Final status                                                               |
| `invalid`    | Not paid | Irregular or unsupported payment event requiring exception handling. Final status                                   |
| `abandoned`  | Not paid | Payment was created but never started, and was closed by Bead. Final status                                         |

### 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 outcome is not yet known.

Do not treat `processing` as a paid payment.

### Completed

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

### 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.

Treat `overpaid` as a paid payment and fulfill the order. The requested amount settles to the merchant. The excess, `amounts.paid` minus `amounts.requested`, is returned to the payer through the reclaim process. `statusCode` stays `overpaid`. Reclaim progress, including completion, arrives in webhooks as changes to `refundLifecycleStatus`.

### 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.

Treat `underpaid` as a declined payment. Do not fulfill, and offer the customer a new payment. The amount sent is returned to the payer through the reclaim process. `statusCode` stays `underpaid`. Reclaim progress, including completion, arrives in webhooks as changes to `refundLifecycleStatus`.

### 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.

### Abandoned

An `abandoned` payment was created, but the customer never started a payment attempt. Bead closes these payments automatically after the hosted payment session lifetime. Nothing was requested, so there is nothing to reclaim.

### Reclaim progress

Every status except `created` and `processing` is final. A reclaim does not change `statusCode`. Bead sends a payment webhook each time `refundLifecycleStatus` changes, with the payment's final `statusCode` unchanged. `partiallyRefunded` and `fullyRefunded` are reserved values.

See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status).

### Implementation guidance

When mapping these payloads to your integration:

* Use `statusCode` as the primary state indicator
* Store `paymentId` with your internal payment, invoice, or order record and use it as the primary key for correlation
* Use `paymentId` when calling `GET /Payments/{paymentId}/tracking`
* Use `paymentId` to correlate Create Payment responses, webhook events, reporting records, and support workflows
* Do not build new logic on `trackingId`. It is a legacy identifier kept for backward compatibility
* Compare `amounts.requested` and `amounts.paid` when handling `overpaid` or `underpaid`
* Treat `processing` as pending
* Treat `completed` and `overpaid` as paid for the requested amount, and fulfill
* Treat `underpaid`, `expired`, `invalid`, `cancelled`, and `abandoned` as not paid
* Treat later reclaim webhooks as record updates. Do not reverse 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/{paymentId}/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)
* [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status)


# 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 updates to your system. Instead of polling, your server receives an HTTP `POST` whenever a payment's `statusCode` changes, and whenever the progress of a reclaim return to the payer 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 is paid (`completed` or `overpaid`)
* react to `underpaid`, `expired`, `invalid`, or `cancelled` outcomes
* follow a reclaim return to the payer through `refundLifecycleStatus`
* trigger downstream workflows such as fulfilment, 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 or when its reclaim progress 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`, 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, and one event per reclaim progress 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=
```

| Field | Description                                                                                                                                         |
| ----- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `t`   | Unix epoch timestamp in **milliseconds** when Bead generated the event                                                                              |
| `s`   | **Base64-encoded** HMAC-SHA256 digest of the signed message `t + "." + rawBody`, computed using the decoded bytes of the terminal's `signingSecret` |

**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**

The example shows an `overpaid` payment after the excess was returned to the payer. `statusCode` stays `overpaid`; `refundLifecycleStatus` shows the completed return. The same payload shape is returned by `GET /Payments/{paymentId}/tracking`.

{% code expandable="true" %}

```json
{
  "paymentId": "pay_d3594f0680964156b21fab60f8573bb4",
  "trackingId": "d3594f0680964156b21fab60f8573bb4",
  "paymentCode": "HM9N44Z43VTW",
  "statusCode": "overpaid",
  "tenderType": "usdcBase",
  "amounts": {
    "requested": {
      "inPaymentCurrency": {
        "amount": 100,
        "currency": {
          "id": 16,
          "code": "USDC_BASE",
          "name": "USDC Base",
          "symbol": "USDC"
        }
      },
      "inRequestedCurrency": {
        "amount": 100,
        "currency": {
          "id": 1,
          "code": "USD",
          "name": "USD",
          "symbol": "$"
        }
      }
    },
    "paid": {
      "inPaymentCurrency": {
        "amount": 105,
        "currency": {
          "id": 16,
          "code": "USDC_BASE",
          "name": "USDC Base",
          "symbol": "USDC"
        }
      }
    }
  },
  "reference": "ORDER123",
  "description": null,
  "receivedTime": "2026-10-06T15:12:44.508Z",
  "terminalId": "69aae38b1f3fe8c6698663dc",
  "merchantId": "664c5e3b0517b0a8a6321c9a",
  "errorMessage": null,
  "originalSenderAddress": "0x8f3c...a91d",
  "conversions": [],
  "refundLifecycleStatus": "completed",
  "refundInitiatedAt": "2026-10-06T13:57:20.207Z",
  "refundCompletedAt": "2026-10-06T15:12:44.211Z",
  "refundFailureReason": null
}
```

{% endcode %}

**Payment status values**

Payment webhook events may include `created`, `processing`, `completed`, `overpaid`, `underpaid`, `expired`, `invalid`, and `cancelled`. Automatic closures by Bead are an exception, described below.

Your system should treat `statusCode` as the primary event classifier. `completed` and `overpaid` mean the payment is paid. `underpaid`, `expired`, `invalid`, and `cancelled` mean it is not paid. Every status other than `created` and `processing` is final: it does not change once reached. See [Payment Statuses](/payments/payment-statuses) for the full outcome table.

> **Payments that Bead closes automatically (`abandoned` and `expired`):** Bead closes unpaid payments after the hosted payment session lifetime. A payment that was created but never started is closed as `abandoned`. A payment where the customer started an attempt but did not complete it before the window closed is closed as `expired`. These automatic closures are not currently delivered as payment webhook events, so a payment can remain in `created` or `processing` on your side after Bead has closed it, with no notification. Set your own timeout for pending orders and reconcile any payment still open at that point with `GET /Payments/{paymentId}/tracking`. See [Payment Statuses](/payments/payment-statuses) and [Why is my payment still Created or In Progress?](/faqs-and-troubleshooting/payments-faqs/why-is-my-payment-still-created-or-in-progress).

**Reclaim progress webhooks**

When a payment ends as `overpaid`, `underpaid`, `expired`, `invalid`, or `cancelled`, Bead returns crypto to the payer through the reclaim process. For `overpaid`, only the excess is returned.

Bead sends a payment webhook each time the payment's `refundLifecycleStatus` changes (`emailSent`, `customerClaimed`, `processing`, `completed`, `failed`, `rejected`). The webhook uses the same payload as other payment webhooks and goes to the terminal webhook URL and to any `webhookUrls` set on the payment.

Reclaim progress webhooks carry the payment's final `statusCode`, unchanged. When the return completes, `refundLifecycleStatus` is `completed` and `refundCompletedAt` is set. An `overpaid` payment stays `overpaid` and an `underpaid` payment stays `underpaid`.

Use these events to update your records. They do not change the fulfillment decision you made on the earlier outcome. See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status).

Partners can also receive reclaim progress for every merchant under them through the `reclaimStatusChanged` partner webhook event. See [Partner Webhooks](/onboarding/partner-webhooks).

**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, correlate status checks with your internal payment or order record, and as part of the recommended key for de-duplicating webhook deliveries (see Idempotency and duplicate handling below).

**`trackingId`**

This is a legacy identifier. `paymentId` replaced `trackingId` as the primary payment identifier in the June 2, 2026 partner-wide update. `trackingId` is deprecated but is still included on webhook payloads for backward compatibility. Do not use `trackingId` for identification, correlation, or de-duplication in new or updated integrations; use `paymentId` instead.

**`paymentCode`**

This is the hosted payment code associated with the checkout.

**`statusCode`**

This tells you the payment's current status.

**`refundLifecycleStatus`**

This tells you where a reclaim return to the payer stands. A value of `null` means the same as `notStarted`.

**`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, confirming `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`, and your reclaim tracking based on `refundLifecycleStatus`.
10. Return a `2xx` response quickly.

**Idempotency and duplicate handling**

Treat payment webhook delivery as at least once.

A good idempotent processing strategy uses `paymentId` + `statusCode` + `refundLifecycleStatus` as the primary key, with the raw body hash or received timestamp optionally stored for debugging. Reclaim progress events carry the payment's final `statusCode`, so a key without `refundLifecycleStatus` would treat them as duplicates. `trackingId` is deprecated and should not be used as part of your idempotency key, even though it is still present on the payload for backward compatibility.

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`.
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 latest 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
* the payment was not closed automatically as `abandoned` or `expired` (see the note above); those closures are not currently sent by webhook

**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. Events for the same `paymentId` and `statusCode` with a different `refundLifecycleStatus` are reclaim progress updates, not duplicates.

**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 and outcomes
* Review [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status) for following a reclaim return
* 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:

* save a merchant application as a Draft with whatever data you have, then submit it when you are ready
* 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 while the application is a Draft
* 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

#### Recommended: Draft and Submit

The recommended starting point for every new integration is Draft and Submit. Collect what you can, send it to the Draft endpoint, attach files if you have them, and submit when the application is ready.

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

The same endpoint serves a full merchant profile or a minimal one. Only `partnerId` and `merchantName` are required, and everything else can be added later with `PUT /merchant-onboarding/applications/drafts/{applicationId}`. Nothing is sent to the signer until you call:

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

In the Draft and Submit flow:

* the request body wraps all merchant fields in `merchantData`
* the signer is identified within `merchantData.stakeholders` with `isApplicationSigner: true`
* format and completeness checks run at submit, so partial data can be saved without errors
* attachments can be uploaded while the application is a Draft
* a Draft that is no longer needed can be discarded with `DELETE /merchant-onboarding/applications/drafts/{applicationId}`

See [Draft and Submit (Recommended)](/onboarding/draft-and-submit) for the full flow, field guidance, and a complete sample.

#### Alternative single call patterns

The two original create endpoints remain available and fully supported. Use them if you already integrated with either one, or if you prefer a single call that creates and sends the application. There are two patterns.

**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

All submission patterns rely on these key inputs:

* signer details
* fee information
* `cryptoEnvironment`

The two single call patterns also accept `submitImmediately`. It is not accepted on Draft requests, where you control timing by calling submit.

For Draft and 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 submitted.

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

For `POST /merchant-onboarding/applications` and `POST /merchant-onboarding/applications-short`, `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 single call flow.

Use `submitImmediately: false` when you need to upload supporting documents before sending the application for signature. New integrations should prefer `POST /merchant-onboarding/applications/drafts` for this purpose.

#### High-level onboarding flow

A typical onboarding workflow depends on whether you use Draft and Submit or one of the single call patterns.

**Draft and Submit flow (recommended)**

1. Create the Draft with the data you have.

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

```json
{
  "merchantData": {
    "partnerId": "partner_123",
    "merchantName": "Northwind Coffee Roasters"
  }
}
```

2. Optionally update the Draft as you collect more data.

```http
PUT /merchant-onboarding/applications/drafts/{applicationId}
```

3. Optionally upload attachments while the application is in Draft status.

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

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

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

5. Submit the Draft for signature.

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

6. Track progress.

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

7. Continue through review and boarding.

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

A typical status path may look like:

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

**Single call flow with immediate submit**

Use this flow if you already integrated with the create endpoints and 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
```

**Single call flow with Draft and attachments**

If you created the application with `POST /merchant-onboarding/applications` and `submitImmediately: false`, the flow is the same as Draft and Submit from the attachment step onward.

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. Submit the Draft application for signature.

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

4. Track progress the same way as the other flows.

#### Application identifiers

All create endpoints return a response that includes `applicationId`. The submit command returns:

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

Store `applicationId` so you can track status, reconcile activity, update or discard Drafts, submit Draft applications, resend applications, and support follow-up workflows later. Every Bead onboarding endpoint takes `applicationId`.

`envelopeId` is an internal signing reference. You do not need it for any Bead API call, and you can ignore it unless Bead support asks for it.

#### Core onboarding endpoints

The core merchant onboarding endpoints are:

```http
POST /merchant-onboarding/applications/drafts
PUT /merchant-onboarding/applications/drafts/{applicationId}
DELETE /merchant-onboarding/applications/drafts/{applicationId}
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 %}

#### Create, Submit, Discard, and Resend

These commands are related but serve different purposes.

| Action                      | Endpoint                                                                                                                   | Use when                                                                                               |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Create Draft (recommended)  | `POST /merchant-onboarding/applications/drafts`                                                                            | You want to save whatever merchant data you have and send the application to the signer later.         |
| Update Draft                | `PUT /merchant-onboarding/applications/drafts/{applicationId}`                                                             | The application is in Draft status and you have more or corrected data. This replaces the stored data. |
| Discard Draft               | `DELETE /merchant-onboarding/applications/drafts/{applicationId}`                                                          | The application is in Draft status and is no longer needed. This permanently deletes it.               |
| 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.           |
| 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 in a single call.                    |
| Create Draft (single call)  | `POST /merchant-onboarding/applications` or `POST /merchant-onboarding/applications-short` with `submitImmediately: false` | You use the original create endpoints and need the application to stay in Draft status.                |
| 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. Start with Draft and Submit. Review Draft and Submit (Recommended) for the flow and a complete sample.
3. Decide which merchant data you will send at Draft creation and which you will add later with an update.
4. Review Fee Configuration for Onboarding Applications so your submitted fees match the merchant's commercial agreement.
5. If you have supporting documents, review Application Attachments and upload them while the application is a Draft.
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.

If you already integrated with `POST /merchant-onboarding/applications` or `POST /merchant-onboarding/applications-short`, you can keep using them. Review Submit Application for those flows.

#### Related pages

[**Draft and Submit (Recommended)**](/onboarding/draft-and-submit)

The recommended onboarding pattern. Save a Draft with the data you have, attach files, update as needed, and submit when ready.

Includes the endpoint set, strict field names, what submit checks, discard behavior, and full and minimal samples.

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

How to create full and short onboarding applications with the original single call endpoints.

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 you have documents to include before signature.

[**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/partner-webhooks)

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

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

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


# Draft and Submit (Recommended)

Draft and Submit is the recommended way to start every merchant onboarding application.

Collect what you have, save it as a Draft with a single call, attach supporting PDFs if you have them, and submit the application for signature when you are ready. The same pattern works whether you hold a complete merchant profile or only a merchant name.

{% hint style="success" %}
**Recommended starting point.** `POST /merchant-onboarding/applications` and `POST /merchant-onboarding/applications-short` remain available and fully supported. If you already integrated with either one, nothing changes for you. See [Submit Application](/onboarding/submit-application) for those flows.
{% endhint %}

#### Why Draft and Submit

* **One pattern for full and partial data.** Only `partnerId` and `merchantName` are required to create a Draft. Send everything you have today and add the rest later.
* **Nothing reaches the signer until you say so.** A Draft is never sent for signature until you call submit.
* **Attachments before signature.** Upload PDF documents while the application is a Draft.
* **Safe to correct.** Replace the stored data with `PUT`, or discard the Draft entirely.
* **Format and completeness checks wait for submit.** Draft saves do not apply format checks such as EIN or email format, so you can save incomplete data without errors.

#### The flow

1. **Create the Draft.** `POST /merchant-onboarding/applications/drafts` with the data you have inside `merchantData`. Store the returned `applicationId`.
2. **Update the Draft as you collect more data (optional).** `PUT /merchant-onboarding/applications/drafts/{applicationId}` with the full `merchantData`.
3. **Attach documents (optional).** `POST /merchant-onboarding/applications/{applicationId}/attachments`. See [Application Attachments](/onboarding/application-attachments).
4. **Submit.** `POST /merchant-onboarding/applications/{applicationId}/submit` with no body. Bead sends the application to the signer.
5. **Track progress.** Poll [Get Status](/onboarding/get-status), subscribe to [webhooks](/onboarding/partner-webhooks), or both.

A typical status path looks like this:

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

#### Endpoints

All endpoints use API key authentication with the `X-Api-Key` header.

| Action            | Endpoint                                                                              | Notes                                                             |
| ----------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| Create Draft      | `POST /merchant-onboarding/applications/drafts`                                       | Body is `{ "merchantData": { ... } }`. Returns `applicationId`.   |
| Update Draft      | `PUT /merchant-onboarding/applications/drafts/{applicationId}`                        | Full replace of the stored data. Returns the updated application. |
| Discard Draft     | `DELETE /merchant-onboarding/applications/drafts/{applicationId}`                     | Returns `204`. Permanently deletes the Draft and its attachments. |
| Upload attachment | `POST /merchant-onboarding/applications/{applicationId}/attachments`                  | PDF only. Draft status only.                                      |
| List attachments  | `GET /merchant-onboarding/applications/{applicationId}/attachments`                   | Verify the package before submitting.                             |
| Delete attachment | `DELETE /merchant-onboarding/applications/{applicationId}/attachments/{attachmentId}` | Draft status only.                                                |
| Submit            | `POST /merchant-onboarding/applications/{applicationId}/submit`                       | No request body. Draft status only.                               |

{% hint style="warning" %}
`PUT /merchant-onboarding/applications/drafts/{applicationId}` updates a Draft. `PUT /merchant-onboarding/applications/{applicationId}` is a different endpoint that resends an application that is already in the signer facing flow. See [Resend Application](/onboarding/resend-application).
{% endhint %}

#### Request shape

The request body wraps all merchant fields in `merchantData`. The field names inside `merchantData` are the same names used by `POST /merchant-onboarding/applications`, including `feeInformation`, `stakeholders`, and `cryptoEnvironment`.

```json
{
  "merchantData": {
    "partnerId": "partner_123",
    "merchantName": "Northwind Coffee Roasters"
  }
}
```

**Required fields**

| Field                       | Description                   |
| --------------------------- | ----------------------------- |
| `merchantData.partnerId`    | Your Bead partner identifier. |
| `merchantData.merchantName` | The merchant name.            |

Every other field is optional when you save a Draft.

**Strict field names**

Draft requests reject unknown properties with a `400`. This applies to the request wrapper, to `merchantData`, and to each stakeholder. For example, a flat body with `partnerId` at the top level fails with `'partnerId' is not a recognized property.` Always nest merchant fields inside `merchantData`, and use the exact field names shown in the full sample below.

**Field notes**

* Addresses use `address1`, `address2`, `city`, `region`, `country`, and `postalCode`.
* The point of contact uses `phoneNumber`, and so do stakeholders.
* `submitImmediately` is not accepted on Draft requests. You control timing by calling submit.
* Send at most 5 stakeholders, with at most one marked `isApplicationSigner: true`.
* `feeInformation` is optional on a Draft, but when you include it, it is validated. See [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications).
* Set exactly one of `isLegalAddress`, `isOperatingAddress`, and `isVirtualAddress` to `true`. These flags drive the address type shown on the application. Send `businessMailingAddress` when `isOperatingAddress` is `true`.
* `sameAsRegisteredAddress` is accepted but not stored. Send the address you want recorded in `businessMailingAddress`.
* `hasNoOwners: true` tells Bead that no individual owns 25 percent or more of the business.

#### Minimal sample

The smallest valid Draft contains only the two required fields.

```bash
curl --request POST \
  --url https://api.test.devs.beadpay.io/merchant-onboarding/applications/drafts \
  --header "X-Api-Key: {apiKey}" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{
    "merchantData": {
      "partnerId": "partner_123",
      "merchantName": "Northwind Coffee Roasters"
    }
  }'
```

Response:

```json
{
  "applicationId": "app-123exampleid"
}
```

Store `applicationId`. Every later call on the application uses it. The `envelopeId` and `status` values are not populated until the application is submitted.

#### Full sample

This Draft carries a complete merchant profile with a signer, banking details, and fee information. Use it as the reference for field names and shapes.

{% code expandable="true" %}

```json
{
  "merchantData": {
    "partnerId": "partner_123",
    "merchantName": "Northwind Coffee Roasters",
    "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",
    "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
          }
        }
      ]
    },
    "cryptoEnvironment": "sandbox"
  }
}
```

{% endcode %}

The sample prices PayPal only. For each additional wallet or BNPL tender you price, add an `inStore` row and an `online` row in the same shape. See [Sample Payload](/onboarding/sample-payload) for the complete fee reference.

Send it:

```bash
curl --request POST \
  --url https://api.test.devs.beadpay.io/merchant-onboarding/applications/drafts \
  --header "X-Api-Key: {apiKey}" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data @northwind-draft.json
```

#### Update a Draft

Send the complete `merchantData` again to replace what is stored. This is a full replace, not a merge, so any field you leave out is cleared.

The request body is identical to Create a Draft. `applicationId` appears only in the URL.

```bash
curl --request PUT \
  --url https://api.test.devs.beadpay.io/merchant-onboarding/applications/drafts/app-123exampleid \
  --header "X-Api-Key: {apiKey}" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data @northwind-draft-updated.json
```

The response is the updated application. The same strict field name rules apply as on create.

#### Attach documents

Attachments are PDF only and can be added or deleted only while the application is a Draft. Upload each file with a `category`, and include `stakeholderIndex` for stakeholder documents.

```bash
curl --request POST \
  --url https://api.test.devs.beadpay.io/merchant-onboarding/applications/app-123exampleid/attachments \
  --header "X-Api-Key: {apiKey}" \
  --form "file=@proof-of-bank-account.pdf" \
  --form "category=proofOfBankAccount"
```

See [Application Attachments](/onboarding/application-attachments) for categories, size limits, and examples.

#### Submit

When the Draft is ready, submit it. The request has no body.

```bash
curl --request POST \
  --url https://api.test.devs.beadpay.io/merchant-onboarding/applications/app-123exampleid/submit \
  --header "X-Api-Key: {apiKey}" \
  --header "Accept: application/json"
```

Response:

```json
{
  "applicationId": "app-123exampleid",
  "envelopeId": "env-1111-2222-3333-4444",
  "status": "signing"
}
```

`envelopeId` is an internal signing reference. You do not need it for any Bead API call.

**What submit checks**

Submit is where completeness is enforced. The request is rejected if any of these are not met:

* the application is in Draft status
* one stakeholder is marked `isApplicationSigner: true`
* attachments, where provided, are PDF files
* each stakeholder has a date of birth
* fee information is complete, including the ACH settlement fixed fee and the settlement return fixed fee
* fees meet the fee group and partner sell rate checks configured for your partner
* the signer email is unique

If submit fails, fix the data with `PUT`, then submit again. The Draft stays in Draft status until a submit succeeds.

**Remaining fields are completed by the signer**

Any application fields still blank at submit can be completed by the signer in Bead's hosted signing experience. Send as much as you have so the signer mainly reviews, confirms, and signs.

#### Discard a Draft

Discard a Draft that is no longer needed.

```bash
curl --request DELETE \
  --url https://api.test.devs.beadpay.io/merchant-onboarding/applications/drafts/app-123exampleid \
  --header "X-Api-Key: {apiKey}"
```

A successful discard returns `204 No Content` and permanently deletes the Draft and its attachments. Discard works only on Draft applications. Calling it on any other status returns `400` with the message `Only Draft applications can be discarded`. To stop an application that has left Draft status, see [Cancel Application](/onboarding/cancel-application).

#### Error handling

| Scenario                                                          | Result                                                         |
| ----------------------------------------------------------------- | -------------------------------------------------------------- |
| Missing `partnerId` or `merchantName`                             | `400` validation error.                                        |
| Unknown property in the wrapper, `merchantData`, or a stakeholder | `400`, for example `'partnerId' is not a recognized property.` |
| More than 5 stakeholders, or more than one signer                 | `400` validation error.                                        |
| `feeInformation` present but invalid                              | `400` validation error.                                        |
| Submit on an application that is not a Draft                      | Request rejected.                                              |
| Submit with missing signer, dates of birth, or fee information    | Request rejected with the failing check.                       |
| Discard on a non Draft application                                | `400`, `Only Draft applications can be discarded`.             |
| Unknown `applicationId`                                           | `404 Not Found`.                                               |
| Missing or invalid API key                                        | `401 Unauthorized`.                                            |

#### Best practices

* Start with Draft and Submit for every new integration.
* Save a Draft as soon as you have a merchant name, then enrich it as data arrives.
* Always nest merchant fields in `merchantData` and use the exact field names from the full sample.
* Resend the complete `merchantData` on every `PUT`.
* Mark one stakeholder as the signer before you submit.
* Upload and list attachments before you submit, because they cannot change afterward.
* Store `applicationId` immediately after create.
* Discard abandoned Drafts so your records stay clean.
* Use `GET /merchant-onboarding/applications/{applicationId}` and webhooks to track the application after submit.

#### Related pages

* [Submit Application](/onboarding/submit-application)
* [Sample Payload](/onboarding/sample-payload)
* [Application Attachments](/onboarding/application-attachments)
* [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications)
* [Get Status](/onboarding/get-status)
* [Resend Application](/onboarding/resend-application)
* [Cancel Application](/onboarding/cancel-application)
* [Webhooks for Application Events](/onboarding/partner-webhooks)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)


# Submit Application

{% hint style="success" %}
**Starting a new integration?** Use [Draft and Submit (Recommended)](/onboarding/draft-and-submit). Save a Draft with `POST /merchant-onboarding/applications/drafts`, attach files if you have them, then call `POST /merchant-onboarding/applications/{applicationId}/submit`. The endpoints on this page remain available and fully supported for integrations that already use them.
{% endhint %}

Create a new merchant onboarding application through the API with a single call.

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

Choose [Draft and Submit](/onboarding/draft-and-submit) when:

* you want one pattern that works whether you have complete or partial merchant data
* you want to save data as you collect it and send the application to the signer later
* you want to upload attachments before signature without setting `submitImmediately`

For all 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.

<table><thead><tr><th width="136">Value</th><th>Behavior</th></tr></thead><tbody><tr><td>omitted</td><td>Bead preserves the existing default behavior and sends the application to the signer immediately.</td></tr><tr><td><code>true</code></td><td>Bead creates the application and immediately starts the signer flow.</td></tr><tr><td><code>false</code></td><td>Bead creates the application in Draft status so attachments can be uploaded before the signer receives it.</td></tr></tbody></table>

Use `submitImmediately: false` when you need to upload supporting documents through the attachment endpoints before sending the application for signature. The dedicated Draft endpoint, `POST /merchant-onboarding/applications/drafts`, is the recommended way to create a Draft. See [Draft and Submit (Recommended)](/onboarding/draft-and-submit).

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"`. The value can still change while the application is in flight — what matters is the value at boarding. Adding an environment after boarding is account maintenance, not a resubmission, but it will likely trigger a new compliance review for the added environment, so it still pays to get this right up front. 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"`. The value can still change while the application is in flight — what matters is the value at boarding. Adding an environment after boarding is account maintenance, not a resubmission, but it will likely trigger a new compliance review for the added environment, so it still pays to get this right up front. 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.

#### Error responses

{% hint style="info" %} **Confirmed for these two endpoints only.** The shape below has been verified against real responses from `POST /merchant-onboarding/applications` and `POST /merchant-onboarding/applications-short`. It has not been confirmed for other onboarding endpoints or for status codes other than `400` on these two — treat those as unconfirmed until validated. {% endhint %}

A `400` response from either submit endpoint indicates that one or more fields in your request failed validation. The response body is an RFC 9110 Problem Details object, not a Bead-specific envelope:

```json
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Customer.FirstName": [
      "The FirstName field is required."
    ]
  },
  "traceId": "00-0000000000000000000000000000000-0000000000000000-00"
}
```

**Fields**

* `type`\
  A URL identifying the RFC 9110 status section for this response (for example, `.../rfc9110#section-15.5.1` for a `400`). Varies by status code, not by which field failed.
* `title`\
  A short, human-readable summary of the error class — for a validation failure, always `"One or more validation errors occurred."`.
* `status`\
  The HTTP status code, duplicated in the body.
* `errors`\
  An object, not an array. Each key is the **PascalCase, dot-delimited path** of the request field that failed — matching the property names in the underlying request model rather than your original JSON casing (for example, `Customer.FirstName` for a nested field). Each value is an **array of message strings** for that field; a field can carry more than one message if it fails more than one rule.
* `traceId`\
  A [W3C trace-context](https://www.w3.org/TR/trace-context/#traceparent-header) formatted identifier. Always include this when contacting Bead support about a validation failure.

**What this means for your integration**

* Don't parse for a `code`, `error`, `message`, or `details` field on a validation response from these two endpoints — they are not present. Parse `errors` as a map of field path → message array.
* Field paths reflect the request's underlying model naming (PascalCase, dot-delimited), which may not exactly match the camelCase field names you sent — build your field-path matching accordingly, or match on a case-insensitive, delimiter-normalized basis.
* Log `traceId` alongside the failing field paths. Support can look up the exact request by trace ID.
* See [Field Validation Rules](/onboarding/field-validation-rules) for the full list of checks that can populate `errors`.

This confirmed shape supersedes any different error example previously shown for these two endpoints, including on [Error Codes](/reference-guide/core-concepts/error-codes) and [How do I resolve a 400 ValidationError?](/faqs-and-troubleshooting/webhooks-and-error-codes/how-do-i-resolve-a-400-validationerror) — those pages are being reconciled separately.

#### Best practices

* For new integrations, start with [Draft and Submit](/onboarding/draft-and-submit).
* 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.** Adding an environment after boarding is account maintenance rather than a resubmission, but expect a new compliance review scoped to the added environment — so it's still worth getting right at boarding. Don't default to `"both"` as a hedge: it attracts more scrutiny at review than a scoped, correct selection.
* 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.
* Parse `400` responses from these two endpoints as the confirmed `errors` map above, not a `details` array. See [Error responses](#error-responses).

#### Related pages

* [Draft and Submit (Recommended)](/onboarding/draft-and-submit)
* [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/partner-webhooks)
* [Sample Payload](/onboarding/sample-payload)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox)
* [Field Validation Rules](/onboarding/field-validation-rules)


# 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.

**Check the partner's fee floor first**

Before building `feeInformation`, call `GET /Partners/{id}/fee-schedule` for the merchant's partner. The `sellRate` values it returns are the **floor** — onboarding rejects a merchant fee `sellRate` that falls below the resolved partner fee schedule.

The schedule may be inherited from an ancestor partner (see [Partner Management → Get a partner's fee schedule](/entity-management/partner-management#get-a-partners-fee-schedule) for how resolution works), so always resolve it for the specific partner the merchant is boarding under rather than assuming a flat, org-wide floor.

**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. resolve the partner's fee schedule floor via `GET /Partners/{id}/fee-schedule` before pricing the merchant
2. determine the merchant's agreed commercial setup, confirming it does not fall below that floor
3. build `feeInformation` from that agreement
4. include all intended settlement, crypto, and wallet fees
5. submit the application only after fee configuration is complete
6. treat missing wallet fee entries as a configuration issue, not as something the signer will fix later

**Common mistakes**

**Pricing below the partner's floor**

If a merchant fee `sellRate` is set below the partner's resolved fee schedule (see [Check the partner's fee floor first](#check-the-partners-fee-floor-first)), onboarding will reject it. Resolve the floor before pricing rather than reacting to the rejection.

**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)
* [Partner Management → Get a partner's fee schedule](/entity-management/partner-management#get-a-partners-fee-schedule)
* [Field Validation Rules](/onboarding/field-validation-rules)


# 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. The recommended way to create an application in Draft status is `POST /merchant-onboarding/applications/drafts`. You can also create a Draft by setting `submitImmediately` to `false` on `POST /merchant-onboarding/applications` or `POST /merchant-onboarding/applications-short`.

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.

### File requirements

| Requirement         | Value                                                                          |
| ------------------- | ------------------------------------------------------------------------------ |
| Accepted file type  | PDF only. JPEG, PNG, and other image formats are not accepted.                 |
| Maximum upload size | 25 MB per file.                                                                |
| Maximum stored size | 10 MB per file. The stored copy of each attachment must fit within this limit. |

Convert images to PDF before uploading, and keep each PDF small enough to meet the stored size limit. A file that is not a PDF, or that exceeds the limits, is rejected.

### Attachment workflow

The attachment workflow has four steps.

1. Create the application in Draft status.

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

```json
{
  "merchantData": {
    "partnerId": "partner_123",
    "merchantName": "Northwind Coffee Roasters"
  }
}
```

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.

See [Draft and Submit (Recommended)](/onboarding/draft-and-submit) for the complete flow, including a full Draft sample.

### Create the application in Draft status

The recommended method is the Draft endpoint. Send the merchant data you have inside `merchantData`. Only `partnerId` and `merchantName` are required.

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

If your integration already uses the original create endpoints, set `submitImmediately` to `false` instead.

```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 and for every later call on the application.

```json
{
  "applicationId": "app_123",
  "envelopeId": "env_456",
  "status": "draft"
}
```

`envelopeId` is an internal signing reference. You do not need it for any Bead API call, so you can ignore it. Always use `applicationId` to identify the application.

### 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 document to upload. See File requirements above.        |
| `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.pdf" \
  --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`

If you update the Draft with `PUT /merchant-onboarding/applications/drafts/{applicationId}`, keep the stakeholder order consistent so existing `stakeholderIndex` values still point to the right person.

### 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.pdf",
    "contentType": "application/pdf",
    "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.

If you discard the whole Draft with `DELETE /merchant-onboarding/applications/drafts/{applicationId}`, all of its attachments are permanently deleted with it.

### 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 a file that is not a PDF                          | The request fails validation.                                                                                                                |
| Uploading a file over the size limits                       | The request fails validation.                                                                                                                |
| Downloading an unknown `attachmentId`                       | The request returns not found.                                                                                                               |

### Best practices

* Create the application with `POST /merchant-onboarding/applications/drafts` whenever attachments are required.
* Upload all required documents before submitting the Draft application for signature.
* Upload PDF files only, and keep each file within the size limits.
* 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

* [Draft and Submit (Recommended)](/onboarding/draft-and-submit)
* [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/partner-webhooks)
* [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`.

> **See a real failure response?** Field paths in `errors` (for example, `Customer.FirstName`) map to the fields documented below. See [Submit Application → Error responses](/onboarding/submit-application#error-responses) for the confirmed response shape from `POST /merchant-onboarding/applications` and `POST /merchant-onboarding/applications-short`.

**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.

**Fee sell rates meet the partner's fee-schedule floor**

Every merchant `sellRate` in `feeInformation` must be at or above the resolved partner fee schedule's `sellRate` for that same fee. A merchant sell rate below the partner's floor is rejected.

* **Applies to:** `feeInformation.achSettlementFixedFee`, `feeInformation.settlementReturnFixedFee`, `feeInformation.monthlyMaintenanceFee`, `feeInformation.bitcoinFee`, `feeInformation.stablecoinFee`, `feeInformation.digitalWalletsFees[]`
* **Fix:** Resolve the partner's floor with [`GET /Partners/{id}/fee-schedule`](/entity-management/partner-management#get-a-partners-fee-schedule) before pricing the merchant — the schedule may be inherited from an ancestor partner, so always resolve it for the specific partner the merchant is boarding under. See [Fee Configuration for Onboarding Applications → Check the partner's fee floor first](/onboarding/fee-configuration-for-onboarding-applications#check-the-partners-fee-floor-first).

**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)
* [Partner Management → Get a partner's fee schedule](/entity-management/partner-management#get-a-partners-fee-schedule)


# 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: uniqueness 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`
* 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: uniqueness 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 `applicationId` and `status` immediately. `envelopeId` is an internal signing reference that you do not need for any Bead API call.

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: uniqueness 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` 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/partner-webhooks)
* [Sample Payload](/onboarding/sample-payload)
* [Test data: uniqueness 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 completed the application
* determine whether the application is still under review or boarding
* retrieve the onboarded merchant identifier after approval
* detect that an application needs changes or was rejected (the reason for a change request is sent in the `applicationChangesRequested` webhook)
* 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.                                                                                     |
| `onboardingExceptions` | Present on every response. For API integrations it is always an empty array. See [Why an application needs changes](#why-an-application-needs-changes) for how to find out why an application needs attention. |

These fields usually tell you whether the application is still in progress, needs attention, or is ready for downstream merchant setup and usage.

The application `id` is the identifier to store and use. Any envelope identifier returned when an application is created is an internal signing reference and is not needed for any Bead API call.

#### 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",
  "onboardingExceptions": []
}
```

#### Understanding application status

The `status` field represents the overall onboarding state of the application.

> **Status casing.** This endpoint and the list endpoint currently return `status` with an initial capital letter, for example `Signing` or `ChangesRequested`. Webhook payloads use camelCase, for example `signing` or `changesRequested`. The status names in this page are written in camelCase. Compare status values without regard to case, and do not depend on one casing.

There is no `failed` status. An application that needs correction or was declined moves to `changesRequested` or `rejected` instead. See [Why an application needs changes](#why-an-application-needs-changes) for how to find out why.

| 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. The reason is sent in the `applicationChangesRequested` webhook, in `data.reason`.            | Read the reason from the webhook, collect updated information, and resend the application. See Resolving changesRequested below. |
| `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 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.                                                                                                |
| `viewed`           | The signer has opened the application.                                                                                                        | Continue monitoring status or webhooks.                                                                                          |
| `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. No rejection reason is provided through the API, and no webhook is sent. | Contact your Bead team to determine next steps.                                                                                  |

These are the values `status` can return today: `draft`, `submitted`, `changesRequested`, `resubmitted`, `signing`, `signed`, `reviewing`, `reviewed`, `viewed`, `boarding`, `boarded`, `withdrawn`, and `rejected`.

Additional status values may be added over time. Existing statuses keep their current meaning when new ones are introduced. Build your integration to tolerate a `status` value it does not recognize: log it, keep the last state you recognized, and do not fail. Check again later, or use webhooks, to pick up the next status.

#### 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 → 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 → 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 the `applicationChangesRequested` webhook to learn *why* an application needs changes
* 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. It also covers the case where an onboarding webhook delivery is dropped after its short retry window. See [Partner Webhooks](/onboarding/partner-webhooks).

#### 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

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.

Where these details carry their own `status` value, the possible values are:

| Detail                      | Possible `status` values                                                                                                                                |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agreementApplication`      | `unsent`, `sent`, `completed`, `correct`, `created`, `declined`, `deleted`, `delivered`, `signed`, `template`, `timedOut`, `voided`, `resent`, `failed` |
| `bankingApplication`        | `pending`, `boarded`, `failed`                                                                                                                          |
| `cryptoServicesApplication` | `customerCreated`, `customerActivated`, `failed`                                                                                                        |

Treat these as diagnostic detail. The overall application `status` remains the source of truth for where the application stands.

#### Why an application needs changes

For API integrations, the reason an application needs changes is delivered in the `applicationChangesRequested` webhook, in the `data.reason` field. Store it when the event arrives, and show it to the merchant or reseller in your own portal or workflow. See [Partner Webhooks](/onboarding/partner-webhooks) for the payload.

Two other places might look like they carry the reason, but they do not for API integrations:

* `onboardingExceptions` on this endpoint is always returned as an empty array.
* Application history notes contain standard summary text only, for example "Application returned for changes before boarding". They do not describe what needs to be corrected.

A rejected application has no webhook and no reason through the API. Detect it by checking `status`, and contact your Bead team for next steps.

Bead does not send email or other notifications to the signer or the partner when an application moves to `changesRequested`, `rejected`, or `boarded`. Use webhooks and this endpoint to drive any notifications in your own system.

#### Onboarding exceptions

`onboardingExceptions` is returned directly on this endpoint. There is no separate exceptions endpoint. The field is always present on the response.

Detailed exception records are internal to Bead. For API integrations the array is returned empty, including when the application is in `changesRequested` or `rejected`. An empty array does not mean the application is moving forward, so always check `status`, and use the `applicationChangesRequested` webhook to learn the reason an application needs changes.

#### Resolving changesRequested

An application in `changesRequested` does not move forward on its own. It stays stalled until the corrected application is resent.

To resolve it:

1. Read the reason from the `data.reason` field of the `applicationChangesRequested` webhook. The reason is not available through this endpoint or the application history. If you missed the event, contact your Bead team.
2. Pass the reason on to the merchant or reseller in your own portal or workflow, so they know exactly what to correct.
3. Once the information is corrected, use [Resend Application](/onboarding/resend-application) to send the application again.
4. Keep checking status. The application returns to the signing flow after it is resent.

An `applicationChangesRequested` webhook is sent when an application enters this state, if your registration includes that event type. Because a dropped webhook delivery is not resent, store the reason when the event arrives.

#### 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.

The response is an array with one entry for each change. Each entry has these fields:

| Field           | Type           | Description                                                                                                                                               |
| --------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `applicationId` | string         | The application the entry belongs to.                                                                                                                     |
| `action`        | string         | What happened, for example a status change or a resend.                                                                                                   |
| `fromStatus`    | string or null | The `status` before the change. `null` when there was no earlier status.                                                                                  |
| `toStatus`      | string         | The `status` after the change. Uses the same values as the status table above.                                                                            |
| `performedBy`   | string or null | Who or what made the change. Not returned to API integrations, so expect `null`.                                                                          |
| `notes`         | string or null | Free text recorded with the change. For API integrations this is standard summary text only. It does not include the reason an application needs changes. |
| `timestamp`     | string         | When the change happened, in UTC ISO-8601 format.                                                                                                         |

Example history entry:

```json
{
  "applicationId": "app-123exampleid",
  "action": "statusChanged",
  "fromStatus": "reviewing",
  "toStatus": "changesRequested",
  "performedBy": null,
  "notes": "Application returned for changes before boarding",
  "timestamp": "2026-08-20T14:32:00Z"
}
```

#### 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. No reason is provided through the API, so contact your Bead team to review next steps. |

#### Best practices

* Store `applicationId` when the application is created.
* Use `applicationId` as the primary lookup key for status checks.
* Compare `status` values without regard to case. This endpoint currently returns a capitalized first letter, and webhook payloads use camelCase.
* Store the `data.reason` from every `applicationChangesRequested` webhook. It is the only place the reason is provided to API integrations.
* Do not rely on `onboardingExceptions` or application history notes to explain why an application needs changes.
* 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. Treat an unrecognized `status` value as informational rather than an error.
* Display clear internal support states for `changesRequested`, `withdrawn`, and `rejected`, using the reason you stored from the webhook for `changesRequested`.
* For Sandbox testing, remember that some steps may require manual advancement by your Bead team.

**Related pages**

* [Submit Application](/onboarding/submit-application)
* [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)
* [Partner Webhooks](/onboarding/partner-webhooks)
* [Sample Payload](/onboarding/sample-payload)
* [Reference Guide](https://developers.bead.xyz/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.

{% hint style="info" %}
**Resend is how a `changesRequested` application moves again.** An application in `changesRequested` stays stalled until it is resent. Read `onboardingExceptions` and the application history first, pass the reason to the merchant or reseller, and then resend. See Resolving changesRequested on [Get Status](/onboarding/get-status).
{% endhint %}

### 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](/onboarding/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"
}
```

Continue tracking the application by `applicationId`. `envelopeId` is an internal signing reference that you do not need for any Bead API call.

### 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 after the information has been corrected. The application stays stalled until it is resent.             |
| `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](/onboarding/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/partner-webhooks)
* [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, or to withdraw a Draft application before it's ever sent to a signer.

This endpoint applies to applications created with any of the onboarding create endpoints:

* Drafts created with `POST /merchant-onboarding/applications/drafts`
* Full applications created with `POST /merchant-onboarding/applications`
* Short applications created with `POST /merchant-onboarding/applications-short`

Cancel revokes the signing token, when one exists, so the signer can no longer complete the current session. It does not delete the application record. The application moves to `withdrawn`.

{% hint style="info" %}
**Cancel vs. Discard.** To permanently delete a Draft and its attachments, use `DELETE /merchant-onboarding/applications/drafts/{applicationId}`. It returns `204` and works only on Draft applications. Cancel keeps the record and marks it `withdrawn`. See [Draft and Submit (Recommended)](/onboarding/draft-and-submit).
{% endhint %}

{% hint style="warning" %} **Cancel is available while an application is in `draft`, `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 `draft`/`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:

* withdraw a Draft application that was created in error and that you want to keep on record, before any signer has been involved (to delete it instead, discard it)
* 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 created in Draft status and is no longer needed
* 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 `draft`, `viewed`, or `signing` status. For Draft applications there is no signer or signing token yet, so cancel simply marks the application `withdrawn` before it ever reaches a signer. 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 has been submitted and is in `submitted`, `changesRequested`, or `resubmitted` status, so 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, in which case use Resend Application instead
* you want to permanently delete a Draft, in which case use the Draft discard command instead

**Endpoint**

```http
POST /merchant-onboarding/{applicationId}/cancel
```

**Path parameter**

<table><thead><tr><th width="207">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, when one exists, so the current signer can no longer access or complete the hosted onboarding and signing experience. For a Draft application, there is no signing token to revoke. Cancel simply moves the application to `withdrawn` before it reaches a signer.

The application record is preserved and is never deleted by cancel. A successful cancel from `draft`, `viewed`, or `signing` always results in the application moving to `withdrawn`. You can retrieve the application afterward to confirm, but the outcome is deterministic, not something you need to check for.

**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 `draft`, `viewed`, or `signing` status.
3. Call `POST /merchant-onboarding/{applicationId}/cancel`.
4. Optionally retrieve the updated status, which will be `withdrawn`.
5. Decide on next steps, whether to create a new application 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. The application's status becomes `withdrawn`.

**Status handling**

**Cancel is valid while the application is in `draft`, `viewed`, or `signing` status.** A successful cancel always results in `withdrawn`. Retrieve the current application status before cancelling and do not attempt this call against any other status.

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

| Status             | Cancellable? | Resulting status | Cancel guidance                                                                                   |
| ------------------ | ------------ | ---------------- | ------------------------------------------------------------------------------------------------- |
| `draft`            | **Yes**      | `withdrawn`      | No signing token exists yet. Cancel marks the application `withdrawn` before it reaches a signer. |
| `submitted`        | No           | n/a              | The application has been submitted. The cancel window has closed.                                 |
| `signing`          | **Yes**      | `withdrawn`      | The signer has not completed the package. Cancel stops the signing session.                       |
| `viewed`           | **Yes**      | `withdrawn`      | The signer opened, but did not complete, the package. Cancel stops the signer proceeding.         |
| `changesRequested` | No           | n/a              | The application was submitted and returned for changes. Cancel is not applicable.                 |
| `resubmitted`      | No           | n/a              | The application was resubmitted after changes. Cancel is not applicable.                          |
| `signed`           | No           | n/a              | The application has already been signed. Cancelling is not applicable.                            |
| `reviewing`        | No           | n/a              | The application is under review. Coordinate with your Bead team if needed.                        |
| `boarding`         | No           | n/a              | The application is in downstream boarding. Coordinate with your Bead team if needed.              |
| `boarded`          | No           | n/a              | Onboarding is complete.                                                                           |
| `withdrawn`        | No           | n/a              | The application is already in a terminal state.                                                   |
| `rejected`         | No           | n/a              | The application is already in a terminal state.                                                   |

**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 `draft`, `viewed`, or `signing` status (already submitted or already terminal) | `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 valid for `draft`, `viewed`, or `signing` status.
* Cancel works on Draft applications too. There is no signing token to revoke, but the application still moves to `withdrawn`.
* To permanently delete a Draft instead of keeping it as `withdrawn`, use `DELETE /merchant-onboarding/applications/drafts/{applicationId}`.
* 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.
* A successful cancel always results in `withdrawn`, so you don't need to poll to find out what happened, though you can retrieve the application afterward to confirm.
* 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**

* [Draft and Submit (Recommended)](/onboarding/draft-and-submit)
* [Submit Application](/onboarding/submit-application)
* [Get Status](/onboarding/get-status)
* [Resend Application](/onboarding/resend-application)
* [Partner Webhooks](/onboarding/partner-webhooks)
* [Sample Payload](/onboarding/sample-payload)
* [Reference Guide](/reference-guide)


# Partner Webhooks

Partner webhooks notify your system about activity at the partner level: progress on merchant onboarding applications, settlements that Bead places on hold for merchants under your partner, and reclaim progress on payments for merchants under your partner.

Instead of relying only on repeated polling, your server receives an HTTP `POST` when one of these events occurs.

Bead has two webhook families:

|                  | Payment webhooks                                  | Partner webhooks (this page)                              |
| ---------------- | ------------------------------------------------- | --------------------------------------------------------- |
| Scope            | One terminal                                      | Your whole partner, across every merchant under it        |
| Events           | Payment status changes and reclaim progress       | Application, settlement, and reclaim events               |
| Configure with   | `PUT /Terminals/{id}/webhook`                     | `PUT /merchant-onboarding/{partnerId}/webhook`            |
| Secret           | `signingSecret`, one per terminal, base64 encoded | `webhookSecret`, one per partner, used exactly as issued  |
| Signature header | `X-Webhook-Signature`                             | `X-Webhook-Signature`, plus the legacy `X-Bead-Signature` |
| Retries          | Up to about 24 hours                              | 5 retries over about a minute                             |
| Details          | [Payment Webhooks](/payments/payment-webhooks)    | This page                                                 |

A partner has one webhook endpoint and one secret. Every partner event type you register is delivered to that single endpoint.

> **Note on naming:** the configuration endpoint path contains `merchant-onboarding` for historical reasons. It configures all partner webhooks, not only application events.

Use partner webhooks when you want to:

* keep your own merchant records in sync with application progress
* trigger internal workflows when an application is signed, returned for changes, or boarded
* learn the reason an application was returned for changes
* learn when a merchant's settlement has been placed on hold
* follow reclaim returns to payers across all of your merchants in one place
* notify internal teams when onboarding or settlement activity requires follow up
* reduce repeated polling against the [Get Status](/onboarding/get-status) endpoint
* support operational dashboards and reconciliation workflows

Partner webhooks are configured at the partner level. Once configured, Bead sends events for the applications and merchants associated with that partner.

Partner webhooks are sent only if your partner has an enabled webhook registration that includes the event type. An event type that is not in your registration is not delivered.

### How partner webhooks work

A typical webhook workflow is:

1. You create or manage onboarding applications and merchants under a partner.
2. You configure a partner webhook endpoint.
3. Bead sends an event to your endpoint when a registered event occurs.
4. Your system validates the request, records the event, and updates internal state.
5. If you need the full current state, call the relevant read endpoint. For application events, call `GET /merchant-onboarding/applications/{applicationId}` using the `applicationId` from the webhook. For settlement events, read the settlement using the `settlementId` from the webhook. For reclaim events, read the payment using the `paymentId` from the webhook.

Webhook payloads are event notifications. They are not intended to replace the [Get Status](/onboarding/get-status) endpoint, the [Settlements](/settlement/settlements) endpoints, or the payment read endpoints as the full source of truth.

### Supported events

Partner webhook configuration supports seven event types in three groups.

#### Application events

| Event type                    | Meaning                                                                                                              | Recommended handling                                                                                                                                               |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `applicationViewed`           | The signer opened the application.                                                                                   | Record the event. No action is required. Call Get Status if you show signer progress.                                                                              |
| `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`         | A valid event type that is not currently sent. You can include it in your registration, but no events are delivered. | No action. Use Get Status to follow review progress.                                                                                                               |
| `applicationChangesRequested` | Bead returned the application for changes.                                                                           | Store `data.reason`, which describes what needs correcting. Then follow Resolving changesRequested on Get Status and resend the application once the fix is made.  |
| `merchantBoarded`             | The merchant has completed boarding.                                                                                 | Call Get Status and store `onboardedMerchantId` for downstream workflows. The event also carries the merchant, default location, and default terminal identifiers. |

#### Settlement events

| Event type       | Meaning                                                      | Recommended handling                                                                                                                                                                    |
| ---------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `settlementHeld` | A settlement for a merchant under your partner entered hold. | Record the event. Read `data.holdReason` for the reason, then review the merchant's settlements. The event is sent when the settlement enters hold, not again while it remains on hold. |

#### Reclaim events

| Event type             | Meaning                                                                                                                                                                                                                                | Recommended handling                                                                                                                                                                                                                                  |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reclaimStatusChanged` | The reclaim return to the payer on a payment for a merchant under your partner changed. Sent each time the payment's `refundLifecycleStatus` changes, including when the return completes. The payment's `statusCode` does not change. | Record the event and update your reclaim tracking from `data.refundLifecycleStatus` and `data.statusCode`. Do not change the order's fulfillment decision. See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status). |

The same reclaim progress is also delivered as a payment webhook to the terminal webhook URL. Register `reclaimStatusChanged` when you want one partner level feed across all of your merchants.

Design your handler to tolerate additional event types in the future. New event types should not break your webhook consumer.

#### What Bead does not send

* **Rejected applications.** There is no webhook when an application is rejected, and no rejection reason is provided through the API. Check for the `rejected` status with [Get Status](/onboarding/get-status).
* **Email or other notifications.** Bead does not email or otherwise notify the signer or the partner when an application moves to `changesRequested`, `rejected`, or `boarded`. Use these webhooks and Get Status to drive any notifications in your own system.

### Configure partner webhooks

To receive partner events, configure a webhook URL for your partner.

You can register, update, or remove a partner 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

These 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 every application and merchant under that partner. |

### Request body

Use a request body with your webhook endpoint URL and the event types you want to receive.

```json
{
  "endpointUrl": "https://partner.example.com/webhooks/partner",
  "eventTypes": [
    "applicationViewed",
    "applicationSigned",
    "applicationReviewed",
    "applicationChangesRequested",
    "merchantBoarded",
    "settlementHeld",
    "reclaimStatusChanged"
  ]
}
```

### Field details

| Field         | Required | Description                                                                                                    |
| ------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `endpointUrl` | Yes      | HTTPS URL of your server that will receive partner webhook callbacks.                                          |
| `eventTypes`  | Yes      | List of event types to deliver to this endpoint. Only the event types you list are sent. See Supported events. |

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/partner",
  "isEnabled": true,
  "webhookSecret": "whsec_example_secret",
  "eventTypes": [
    "applicationViewed",
    "applicationSigned",
    "applicationReviewed",
    "applicationChangesRequested",
    "merchantBoarded",
    "settlementHeld",
    "reclaimStatusChanged"
  ]
}
```

Store the `webhookSecret` securely. Treat it as a secret and do not expose it in client side code.

### Receiving webhook events

After you configure partner webhooks, Bead sends HTTP `POST` requests to your `endpointUrl` when registered events occur.

High level behavior:

* method is `POST`
* content type is JSON
* body contains event metadata plus a summary of the application, settlement, or payment
* your endpoint should return a `2xx` response when processing succeeds

Partner webhook payloads are intentionally lean. They are not full application or merchant records.

Use the webhook payload to:

* identify which application, settlement, or payment 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 current state

Use `GET /merchant-onboarding/applications/{applicationId}` when you need the full current state of an application.

### Delivery and retries

These rules apply to every partner webhook event.

If your endpoint returns a `5xx` response, returns `408`, or cannot be reached because of a network error, Bead retries the delivery up to 5 times with exponential backoff, at roughly 2, 4, 8, 16, and 32 seconds. After the fifth retry the delivery is dropped and is not queued again. Responses in the `4xx` range, other than `408`, are not retried.

This is a short retry window. It is different from the payment webhook policy described in [Webhook Event Reference](/reference-guide/operational-guides/webhook-event-reference), which retries for up to about 24 hours. Do not assume a partner event will be redelivered if your endpoint is down for more than a few seconds.

Because a dropped event is not resent, treat webhooks as a notification only. Reconcile your records by calling [Get Status](/onboarding/get-status) and reading the application history, by reading your merchants' settlements, or by reading partner payment history, especially after any period when your endpoint was unavailable. Note that the reason in an `applicationChangesRequested` event is not available from Get Status or the application history, so store it when the event arrives.

### Security and verification

Treat partner webhooks as a privileged integration path.

Partner webhooks use the same signature standard as payment webhooks. Every delivery carries an `X-Webhook-Signature` header:

```
X-Webhook-Signature: t=1791236941841,s=Zh5JifnVPvYu4ofG92KN6yR3TxDtI+thgOfyHXMZors=
```

| Component | Meaning                                                                         |
| --------- | ------------------------------------------------------------------------------- |
| `t`       | Unix epoch timestamp in **milliseconds** when Bead generated the event          |
| `s`       | **Base64 encoded** HMAC-SHA256 digest of the signed message `t + "." + rawBody` |

For partner webhooks, the HMAC key is your `webhookSecret` exactly as it was returned when you configured the webhook, taken as UTF-8 bytes. Do not base64 decode it. This differs from payment webhooks, where the terminal `signingSecret` is base64 decoded before use.

Because the signed message includes a timestamp, you can reject stale requests. Confirm that `t` is within 5 minutes of the current time to get built in replay protection.

**Legacy `X-Bead-Signature` header.** Partner webhooks also continue to include the earlier `X-Bead-Signature` header. It is a lowercase hexadecimal HMAC-SHA256 digest of the raw request body alone, computed with your `webhookSecret`, and it has no timestamp. It is unchanged and continues to be sent, so integrations that already verify it keep working without any change. New integrations should verify `X-Webhook-Signature`, and existing integrations should plan to move to it, because the legacy header cannot provide replay protection.

Recommended practices:

* use HTTPS for your webhook endpoint
* accept only `POST` on your webhook route
* preserve the raw request body and request headers
* verify `X-Webhook-Signature` and reject stale or invalid requests
* compare digests using constant time comparison
* validate the request body against expected fields
* optionally restrict source IPs or networks where appropriate for your environment

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) for a full verification walkthrough and code samples for both webhook families.

### Payload format

Application, settlement, and reclaim events use different envelopes. Use `type` to tell them apart.

#### Application events

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

**Event specific `data` fields**

Some events add fields to `data`.

| Event                         | Field                    | Type           | Description                                                                                                                                                                                            |
| ----------------------------- | ------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `applicationViewed`           | `data.viewedDate`        | string         | When the signer opened the application.                                                                                                                                                                |
| `applicationChangesRequested` | `data.reason`            | string         | Why Bead returned the application for changes. Show it to the merchant or reseller. The text varies, so do not parse it. It is the only place the reason is provided to API integrations, so store it. |
| `applicationChangesRequested` | `data.requestedDate`     | string         | When changes were requested.                                                                                                                                                                           |
| `merchantBoarded`             | `data.merchantId`        | string         | The merchant identifier created at boarding.                                                                                                                                                           |
| `merchantBoarded`             | `data.defaultLocationId` | string \| null | The default location created for the merchant, when one was created.                                                                                                                                   |
| `merchantBoarded`             | `data.defaultTerminalId` | string \| null | The default terminal created for the merchant, when one was created.                                                                                                                                   |
| `merchantBoarded`             | `data.status`            | string         | The application status when the event was sent.                                                                                                                                                        |
| `merchantBoarded`             | `data.boardedDate`       | string         | When boarding completed.                                                                                                                                                                               |

The `data` object can include additional fields, such as status details that depend on the event. Ignore fields you do not recognize.

**Example application event**

Below is an example `applicationSigned` 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"
  }
}
```

Below is an example `applicationChangesRequested` event. The `reason` text is illustrative.

```json
{
  "id": "evt_example_application_changes_requested_001",
  "type": "applicationChangesRequested",
  "applicationId": "app_example_001",
  "createdAt": "2026-03-31T15:02:44.512004Z",
  "data": {
    "id": "app_example_001",
    "merchantName": "Example Merchant",
    "partnerName": "Example Partner",
    "partnerId": "partner_example_001",
    "partnerExternalId": "example-partner-001",
    "reason": "A required business detail is missing or invalid.",
    "requestedDate": "2026-03-31T15:02:44.511873Z"
  }
}
```

#### Settlement events

Settlement events carry the settlement itself in `data`.

| Field          | Type   | Description                                                                                                                                                                                                   |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`           | string | Unique webhook event identifier. A new value is generated for every send.                                                                                                                                     |
| `type`         | string | `settlementHeld`.                                                                                                                                                                                             |
| `settlementId` | string | Identifier of the settlement that was held.                                                                                                                                                                   |
| `merchantId`   | string | Identifier of the merchant the settlement belongs to.                                                                                                                                                         |
| `createdAt`    | string | Timestamp when the event was created.                                                                                                                                                                         |
| `data`         | object | The settlement record, with the same fields returned by the [Settlements](/settlement/settlements) endpoints. `data.status` shows the hold, and `data.holdReason` carries the reason the settlement was held. |

Settlement events do not include `applicationId`. Correlate them using `settlementId` and `merchantId`.

#### Reclaim events

Reclaim events carry the payment in `data`, using the same payload as a payment webhook.

| Field        | Type   | Description                                                                                                                                                                                                 |
| ------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`         | string | Unique webhook event identifier. A new value is generated for every send.                                                                                                                                   |
| `type`       | string | `reclaimStatusChanged`.                                                                                                                                                                                     |
| `paymentId`  | string | Identifier of the payment whose reclaim changed.                                                                                                                                                            |
| `merchantId` | string | Identifier of the merchant the payment belongs to.                                                                                                                                                          |
| `createdAt`  | string | Timestamp when the event was created.                                                                                                                                                                       |
| `data`       | object | The payment, with the same fields as a [payment webhook](/payments/payment-webhooks), including `statusCode`, `refundLifecycleStatus`, `refundInitiatedAt`, `refundCompletedAt`, and `refundFailureReason`. |

**Example reclaim event**

Below is an example `reclaimStatusChanged` event for an overpaid payment whose excess was returned to the payer. `statusCode` stays `overpaid`. The `data` object is abbreviated.

```json
{
  "id": "evt_5b1f0c2a9e7d4a6c8f3b2d1e0a9c8b7d",
  "type": "reclaimStatusChanged",
  "paymentId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
  "merchantId": "664c5e3b0517b0a8a6321c9a",
  "createdAt": "2026-10-06T14:20:42.118Z",
  "data": {
    "paymentId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
    "paymentCode": "PMT-10452",
    "statusCode": "overpaid",
    "terminalId": "65f0b274f9a4c9a3f8d4b2a1",
    "merchantId": "664c5e3b0517b0a8a6321c9a",
    "refundLifecycleStatus": "completed",
    "refundInitiatedAt": "2026-10-06T13:57:20.207Z",
    "refundCompletedAt": "2026-10-06T14:20:41.000Z",
    "refundFailureReason": null
  }
}
```

Reclaim events do not include `applicationId`. Correlate them using `paymentId` and `merchantId`.

### Field guidance

#### `id`

This is the webhook event identifier. Use it as the primary event level idempotency key for application events.

#### `type`

This tells you what happened.

Supported event types include:

* `applicationViewed`
* `applicationSigned`
* `applicationReviewed` (not currently sent)
* `applicationChangesRequested`
* `merchantBoarded`
* `settlementHeld`
* `reclaimStatusChanged`

Use `type` as the primary event classifier, but do not treat it as the complete current state. Call Get Status when your system needs to know the current application status.

#### `applicationId`

This is the primary application correlation key for application events.

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 read the current state to confirm it.

#### `data`

For application events, 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`
* `createdAt`
* `applicationId` for application events, `settlementId` and `merchantId` for settlement events, or `paymentId` and `merchantId` for reclaim events

In most integrations, you should also store:

* `data.partnerId`
* `data.partnerExternalId`
* `data.merchantName`
* `data.reason` for `applicationChangesRequested` events
* the raw request body
* the request headers
* your processing result

Treat the payload as an event trigger, not a source of full 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 `X-Webhook-Signature` and your `webhookSecret`.
4. Parse the JSON payload.
5. Use `id` as the event identifier, and `applicationId`, `settlementId`, or `paymentId` as the correlation key.
6. Apply idempotent processing so duplicate deliveries do not create duplicate actions.
7. Read the current state when you need it.
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 for application events is:

* primary event key: `id`
* fallback key: `applicationId` + `type` + `createdAt`

For settlement events, the `id` is generated fresh on every send, so a resent event carries a different `id`. Use `settlementId` + `type` as your key instead.

For reclaim events, the `id` is also generated fresh on every send. Use `paymentId` + `data.refundLifecycleStatus` as your key.

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](/onboarding/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 application 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                                                                                          |
| ----------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| `applicationViewed`           | Associated with the `viewed` status.                                                                   | Record the event. Call Get Status if you need the current status.                                           |
| `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`         | Not currently sent. Review progress shows in statuses such as `reviewing`, `reviewed`, and `boarding`. | Use Get Status to follow review progress.                                                                   |
| `applicationChangesRequested` | Associated with the `changesRequested` status.                                                         | Store `data.reason`, show it to the merchant or reseller, then resend the application once the fix is made. |
| `merchantBoarded`             | Usually associated with the `boarded` status and presence of `onboardedMerchantId`.                    | Call Get Status, store `onboardedMerchantId`, and proceed with downstream merchant workflows.               |
| `settlementHeld`              | Not tied to an application status. It relates to a merchant's settlement.                              | Record the event, read `data.holdReason`, and review the merchant's settlements.                            |
| `reclaimStatusChanged`        | Not tied to an application status. It relates to a payment's reclaim return.                           | Record the event and update your reclaim tracking from `data.refundLifecycleStatus`.                        |

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 status.

### Using webhooks with manual risk and review workflows

Webhooks also complement manual risk, review, and operations workflows.

A common pattern is:

1. receive a partner webhook event
2. read 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, for `applicationChangesRequested`, the reason in the event

This is especially useful when an application needs review, follow up, or operational investigation.

### Testing your partner webhooks

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 signature verification passes, and that a tampered request is rejected.
7. Confirm your processing is idempotent.
8. Confirm your system can use `applicationId` to fetch full application state.

Application events fire as test applications move through viewing, signing, and boarding. A `settlementHeld` event is sent only when a settlement actually enters hold, so it cannot be triggered on demand. A `reclaimStatusChanged` event is sent when a payer claims and receives a reclaim, for example after a test payment ends as `overpaid` or `underpaid`. Contact your Bead team if you want to coordinate a settlement or reclaim event test.

Recommended application events to test over time:

* `applicationViewed`
* `applicationSigned`
* `applicationChangesRequested`
* `merchantBoarded`

For a complete Sandbox workflow test, use [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox) and confirm your webhook listener receives the expected lifecycle events as the application moves through signing and boarding.

### Troubleshooting

#### I am not receiving partner webhook events

Check:

* your partner has an enabled webhook registration
* the webhook is configured on the expected `partnerId`
* the applications or merchants belong to 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

If your endpoint was unavailable, remember that a failed delivery is retried only 5 times over about a minute and is then dropped. Call [Get Status](/onboarding/get-status) to catch up on applications, read your merchants' settlements to catch up on holds, and read partner payment history to catch up on reclaims.

#### I am not receiving applicationReviewed events

`applicationReviewed` is a valid event type, but Bead does not currently send it. Use Get Status to follow review progress.

#### 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 `webhookSecret` from your webhook configuration as UTF-8 bytes, and verify the `X-Webhook-Signature` header as described in Security and verification. 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) for the full steps and a code sample.

#### My signature check fails

The most common causes are base64 decoding the `webhookSecret` (partner secrets are used as issued, not decoded), signing the body alone instead of `t + "." + rawBody`, and verifying a parsed and re serialized body instead of the raw bytes.

#### The webhook payload does not include everything I need

This is expected. Partner webhook payloads are designed as lightweight event notifications.

Use them to trigger follow up logic, then read the current state when you need the full record.

#### I received the same webhook more than once

This can happen with at least once webhook delivery.

Use the idempotency keys described above. 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 read the current state using `applicationId`, `settlementId`, or `paymentId` to determine what changed.

### Best practices

* Treat partner webhook events as event notifications, not full records.
* Register the event types your system is prepared to process, and remember that only registered event types are delivered.
* Use `id` as the primary event identifier for application events, `settlementId` for settlement events, and `paymentId` with `data.refundLifecycleStatus` for reclaim events.
* Verify `X-Webhook-Signature` before processing any payload, and reject stale timestamps.
* Store `data.reason` from every `applicationChangesRequested` event, because it is not available from Get Status or the application history.
* Return a `2xx` response quickly.
* Make your consumer idempotent.
* Keep your webhook endpoint HTTPS only.
* Store `webhookSecret` securely.
* Reconcile with Get Status, the application history, merchant settlements, and partner payment history, because a dropped delivery is not resent.
* 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)
* [Settlements](/settlement/settlements)
* [Payment Webhooks](/payments/payment-webhooks)
* [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status)
* [Webhook Event Reference](/reference-guide/operational-guides/webhook-event-reference)
* [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)


# Sample Payload

These examples show current request shapes for onboarding application submission.

Use this page when you need a working reference for:

* creating a Draft application with the recommended Draft endpoint
* creating a short onboarding application
* creating a full onboarding application
* configuring fees
* sending an application directly to the signer
* creating a Draft application with the original create endpoints when attachments are required

For new integrations, start with the Draft endpoint and submit when ready. See [Draft and Submit (Recommended)](/onboarding/draft-and-submit) for the complete flow and a full sample.

For single call Sandbox testing with the original create endpoints, 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, create a Draft, upload attachments while the application is in Draft status, then submit the Draft application for signature.

### Draft application sample (recommended)

The Draft endpoint is:

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

Use this request to save whatever merchant data you have. Only `partnerId` and `merchantName` are required. All merchant fields are sent inside `merchantData`, the signer is identified by `isApplicationSigner: true`, and `submitImmediately` is not used. Unknown properties are rejected with a `400`.

Minimal Draft:

```json
{
  "merchantData": {
    "partnerId": "partner_123",
    "merchantName": "Northwind Coffee Roasters"
  }
}
```

Draft with a signer and fee information:

{% code expandable="true" %}

```json
{
  "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 %}

For a full merchant profile with banking details, all stakeholder fields, and the complete fee set, see the full sample on [Draft and Submit (Recommended)](/onboarding/draft-and-submit).

After the Draft is created, update it with `PUT /merchant-onboarding/applications/drafts/{applicationId}`, upload attachments while the application is in Draft status, then submit it.

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

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

### 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 a single call.

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 with the original create endpoint

If you use `POST /merchant-onboarding/applications`, set `submitImmediately: false` 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. New integrations should prefer `POST /merchant-onboarding/applications/drafts`, shown above.

{% 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, and they are required by the time a Draft is submitted. 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 `applicationId`, which you should store for status tracking, troubleshooting, and follow-up commands. The Draft create endpoint returns `applicationId`. The submit command and the original create endpoints also return an `envelopeId`, an internal signing reference that is not needed for any Bead API call.

```json
{
  "applicationId": "app-123exampleid",
  "envelopeId": "env-1111-2222-3333-4444",
  "status": "signing"
}
```

If the application was created with `submitImmediately: false` on the original create endpoints, the initial status may be `draft`.

```json
{
  "applicationId": "app-123exampleid",
  "envelopeId": "env-1111-2222-3333-4444",
  "status": "draft"
}
```

### Best practices

* For new integrations, create a Draft and submit when ready.
* Use the short application sample for fast single call Sandbox testing.
* Use the full application sample when your system already has most merchant details and you want a single call.
* Use `submitImmediately: true` for the simplest single call signer flow.
* Use a Draft whenever attachments need to be uploaded before the signer receives the application.
* Store `applicationId` immediately after creation. You do not need to store `envelopeId`.
* 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

* [Draft and Submit (Recommended)](/onboarding/draft-and-submit)
* [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/partner-webhooks)


# 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                    |

**Get a partner's fee schedule**

Returns the partner's **effective** fee schedule — the resolved rates a merchant boarded under this partner is priced against.

```http
GET /Partners/{id}/fee-schedule
```

`id` is the ID of the partner to retrieve the fee schedule for.

**How resolution works**

A partner's fee schedule is held by its representing merchant. A partner without a representing merchant of its own inherits the schedule of its nearest ancestor that has one.

Each fee in the response carries two rates:

* `sellRate` — what the resolved partner charges.
* `buyRate` — what the partner pays, which is always its parent account's `sellRate`. This is computed by Bead; it is never submitted.

A fee is included in the response if either side is configured — for example, an inherited `buyRate` can appear even before the partner's own `sellRate` has been set. A side with no configured rate is returned as `null`, never `0`.

**Why this matters for onboarding**

This schedule's `sellRate` values are the **floor** for merchant-level pricing: onboarding rejects a merchant `feeInformation` sell rate that falls below the resolved partner fee schedule. Call this endpoint before assembling `feeInformation` for [Submit Application](/onboarding/submit-application) — see [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications) — so you know the floor before you price a merchant, rather than discovering it as a validation failure. See also [Field Validation Rules → Pricing and tenders](/onboarding/field-validation-rules).

**Example request**

```bash
curl -X GET "https://api.test.devs.beadpay.io/Partners/{partnerId}/fee-schedule" \
-H "X-Api-Key: {adminApiKey}"
```

**Response fields**

| Field                      | Type             | Description                                                                                                                                                                                |
| -------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `partnerId`                | string           | The partner the schedule was requested for.                                                                                                                                                |
| `resolvedFromPartnerId`    | string           | The partner whose representing merchant supplied the sell rates — the requested partner itself, or the nearest ancestor with a representing merchant when the partner has none of its own. |
| `representingMerchantId`   | string           | The representing merchant holding the schedule.                                                                                                                                            |
| `achSettlementFixedFee`    | object or `null` | Settlement ACH fixed fee — `sellRate`, `buyRate`, `isBilledByPartner`.                                                                                                                     |
| `settlementReturnFixedFee` | object or `null` | Settlement return fixed fee — `sellRate`, `buyRate`, `isBilledByPartner`.                                                                                                                  |
| `monthlyMaintenanceFee`    | object or `null` | Monthly maintenance fee — `sellRate`, `buyRate`, `isBilledByPartner`.                                                                                                                      |
| `cryptoFees`               | array or `null`  | One entry per configured crypto tender: `tenderType`, `transactionRate`, `perItemFee`.                                                                                                     |
| `digitalWalletsFees`       | array or `null`  | One entry per configured digital wallet tender: `tenderType`, `transactionRate`, `perItemFee`.                                                                                             |

Each `transactionRate` and `perItemFee` is itself a `{ sellRate, buyRate, isBilledByPartner }` object, following the same null-if-unconfigured rule as the top-level fixed fees.

> **Note:** unlike `feeInformation.digitalWalletsFees` on the onboarding endpoints, entries here are not split by `paymentLocationType` (`inStore` / `online`) — the fee schedule reflects one resolved rate per tender, not per channel.

**Response codes**

| Code | Condition                                                        |
| ---- | ---------------------------------------------------------------- |
| 200  | Fee schedule returned successfully                               |
| 401  | Missing or invalid API key                                       |
| 403  | Not permitted to access this partner                             |
| 404  | Neither the partner nor any ancestor has a representing merchant |

**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)
* [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications)
* [Field Validation Rules](/onboarding/field-validation-rules)


# 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

| Name | Type   | Required | Description                               |
| ---- | ------ | -------- | ----------------------------------------- |
| `id` | string | Yes      | The `merchantId` assigned during boarding |

#### 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}" \
-H "X-Api-Key: {adminApiKey}"
```

#### Successful response

A `200` 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` and `data[].details`.

The `merchant` object carries the core record:

```json
{
  "displayId": "M001667",
  "partnerId": "690e2895852fff2eff800c68",
  "companyName": "Acme Coffee Roasters",
  "description": "West-coast cafe chain",
  "taxNumberType": "ein",
  "isSettlementOnly": false,
  "isCommissionMerchant": false,
  "status": "active",
  "isSplitFundingEnabled": false,
  "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": "usdcBase",
        "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 }
      }
    ]
  },
  "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"
}
```

The `details` object carries the extended KYB record: legal and DBA name, registration date, entity type, NAICS and MCC codes, seasonality, revenue and ticket-size figures, business website, email and phone, mailing address, proof-of-business type, main point of contact, and the merchant's banking details. Banking values are masked in the response.

`receivableCurrencyIds` contains integer IDs from the `/currencies` endpoint. `1` represents USD.

#### Key fields

| Field                               | Description                                                                                                                                                                                                                            |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `merchant`                          | Core merchant record                                                                                                                                                                                                                   |
| `details`                           | Extended KYB details, or `null` if not available                                                                                                                                                                                       |
| `tenderTypes`                       | Default rails every child location inherits unless overridden at the location or terminal                                                                                                                                              |
| `receivableCurrencyIds`             | Settlement currencies, matched against IDs from `/currencies`                                                                                                                                                                          |
| `feeInfo`                           | Nullable. Observed as `null` on merchants. It has the same `crypto` and `card` flat-rate shape used for partner creation and updates. Do not use it for per-asset crypto pricing.                                                      |
| `feeInformation.cryptoFees`         | Per-asset crypto fee breakdown. Each `tenderType` has its own `transactionRate` and `perItemFee`.                                                                                                                                      |
| `feeInformation.digitalWalletsFees` | Per-tender-type fees for wallet rails, or `null` when none are configured.                                                                                                                                                             |
| `status`                            | Boarding state: `unverified`, `active`, `locked`, `restricted`, `inactive`, `suspended`, `terminated`, or `closed`                                                                                                                     |
| `isSplitFundingEnabled`             | Whether the merchant is enrolled in the [Split Funding Program](/split-funding). Returned on the flat `merchant` node, not inside `details`. Set it through [Update Merchant](/entity-management/merchant-management/update-merchant). |
| `memberships`                       | Users and roles that manage this merchant in the dashboard                                                                                                                                                                             |

> **The old field name is not aliased.** `setRebateProgramEnabled` was renamed to `setSplitFundingEnabled`. A request that still sends `setRebateProgramEnabled` is dropped by the deserializer and returns a successful `200` with nothing enabled, so reading the merchant back and checking `isSplitFundingEnabled` is the only reliable confirmation that enrollment applied.

> **The banking details on this record are the merchant's account on file.** On the [Split Funding Program](/split-funding) this is the only account the merchant portion can be settled to. It cannot be overridden on a payment.

#### feeInfo compared with feeInformation.cryptoFees

`feeInfo` and `feeInformation` are both live fields in the API, and `feeInfo` is not deprecated. In practice, `feeInfo` is returned as `null` on merchants. The same `feeInfo` shape 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 is populated on merchants and carries the actual per-`tenderType` breakdown, where 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`.

#### Error responses

| Code  | Condition                                                    |
| ----- | ------------------------------------------------------------ |
| `401` | Missing or invalid API key                                   |
| `403` | Authenticated but not permitted to access this merchant      |
| `404` | Merchant `{id}` does not exist or is not visible to your key |

#### 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.
* Check `isSplitFundingEnabled` on the `merchant` node before sending a split funding block on a payment. See [Split Funding Program](/split-funding).

#### Related pages

* [Update Merchant](/entity-management/merchant-management/update-merchant)
* [Split Funding Program](/split-funding)
* [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, approve, 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, with one exception: `expiresAt` does not revert a split funding enrollment. See [Split funding enrollment](#split-funding-enrollment) below.

This workflow is appropriate for changes to merchant details, fee configuration, merchant status, pinned status, split funding enrollment, or removal requests.

#### Endpoints

```
POST /Merchants/{merchantId}/update-requests
GET  /Merchants/{merchantId}/update-requests
GET  /Merchants/{merchantId}/update-requests/{requestId}
POST /Merchants/{merchantId}/update-requests/{requestId}/submit
POST /Merchants/{merchantId}/update-requests/{requestId}/approve
POST /Merchants/{merchantId}/update-requests/{requestId}/reject
POST /Merchants/{merchantId}/update-requests/{requestId}/cancel
```

***

#### 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 via the separate submit endpoint.

**Authentication**

```
X-Api-Key: {adminApiKey}
```

A partner API key can create, submit, and approve an update request for any merchant in its own hierarchy, including a split funding enrollment change, and can approve its own request. A terminal API key cannot call this endpoint and receives `403 Forbidden`.

**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.                                                                                                          |
| justification   | string               | No       | Reason for the requested change. Recorded on the request and visible to the approver. Use this for an audit-trail note such as "Enrolling in the split funding program." |
| 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. Does not revert a split funding enrollment, see below.                                                         |

**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.                                                                               |
| setSplitFundingEnabled | boolean | Enrolls or removes the merchant from the [Split Funding Program](/split-funding). `true` enrolls; `false` removes. See below. |
| 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.                                                         |

#### Split funding enrollment

Enrollment in the [Split Funding Program](/split-funding) is a single boolean change sent through the same request and approve flow as any other merchant update. The field is named `setSplitFundingEnabled` on the wire.

* `true` enrolls the merchant. Once approved, the merchant can accept a split funding block on payment creation. See [Split Funding Program](/split-funding) for the full field reference and validation rules.
* `false` removes the merchant from the program. This stops new split funding payments. It does not affect payments already taken, in-flight settlements, or amounts already assessed for partner distribution.

Confirm the result with [Get Merchant](/entity-management/merchant-management/get-merchant). The current state is returned as `isSplitFundingEnabled` on the flat `merchant` node of the response, not inside `details`.

> **The previous field names are not aliased.** `setRebateProgramEnabled` was renamed to `setSplitFundingEnabled`, and the read-side flag `isRebateProgramEnabled` to `isSplitFundingEnabled`. A request that still sends the old name is dropped by the deserializer and returns a successful `200` with nothing enabled, so reading the merchant back is the only reliable confirmation that enrollment applied.

> **`expiresAt` and split funding enrollment: two separate effects.**
>
> `expiresAt` does not revert this change. Unlike other update-request changes, a split funding enrollment is not automatically reverted when `expiresAt` passes. To remove a merchant from the program, submit an explicit `setSplitFundingEnabled: false` request rather than relying on `expiresAt`.
>
> Omitting `expiresAt` blocks later requests. The overlap check considers only `approved` and `applied` requests, and treats a null `expiresAt` as an open-ended window. A second update request at the same `precedence` therefore returns `409 Conflict` indefinitely, including the `setSplitFundingEnabled: false` request that would remove the merchant from the program. Set an `expiresAt` on each request, or submit the later request at a higher `precedence`.

Enrollment does not validate the partner payout setup. The representing merchant record, its bank details, its per-tender fee rules and its status are all checked when the first distribution runs, not here. See [Partner distributions](/split-funding#partner-distributions).

**Example request: general merchant update**

```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"
      }
    }
  }'
```

**Example request: split funding enrollment**

```bash
curl -X POST "https://api.test.devs.beadpay.io/Merchants/{merchantId}/update-requests" \
  -H "X-Api-Key: {partnerApiKey}" \
  -H "Content-Type: application/json" \
  -d '{
    "submit": true,
    "precedence": 100,
    "justification": "Enrolling in the split funding program",
    "expiresAt": "2026-12-31T23:59:59Z",
    "changes": {
      "setSplitFundingEnabled": true
    }
  }'
```

**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. A terminal API key always receives this.                          |
| 404  | Merchant does not exist or is not visible to your key.                                                                                         |
| 409  | An approved or applied request already covers this window at the same `precedence`. See [Split funding enrollment](#split-funding-enrollment). |

#### Submit a Draft Request

Submits a request that was created with `submit: false`, moving it from `draft` into the approval queue.

**Authentication**

```
X-Api-Key: {adminApiKey}
```

**Path parameters**

| Name       | Type   | Required | Description                  |
| ---------- | ------ | -------- | ---------------------------- |
| merchantId | string | Yes      | The ID of the merchant.      |
| requestId  | string | Yes      | The ID of the draft request. |

**Example request**

```bash
curl -X POST \
  "https://api.test.devs.beadpay.io/Merchants/{merchantId}/update-requests/{requestId}/submit" \
  -H "X-Api-Key: {adminApiKey}"
```

#### Approve a Request

Approves a `submitted` request. If `effectiveAt` was not set on the request, changes apply immediately.

**Authentication**

```
X-Api-Key: {adminApiKey}
```

A partner API key can approve its own request for a merchant in its hierarchy. Approval is not restricted to a different actor than the requester.

**Path parameters**

| Name       | Type   | Required | Description                       |
| ---------- | ------ | -------- | --------------------------------- |
| merchantId | string | Yes      | The ID of the merchant.           |
| requestId  | string | Yes      | The ID of the request to approve. |

**Request body**

| Field             | Type   | Required | Description                                   |
| ----------------- | ------ | -------- | --------------------------------------------- |
| reason            | string | No       | Optional note recorded with the approval.     |
| approverReference | string | No       | Optional external reference for the approver. |

**Example request**

```bash
curl -X POST \
  "https://api.test.devs.beadpay.io/Merchants/{merchantId}/update-requests/{requestId}/approve" \
  -H "X-Api-Key: {adminApiKey}" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Approved for split funding program"
  }'
```

#### Reject a Request

Rejects a `submitted` request. No changes are applied.

**Authentication**

```
X-Api-Key: {adminApiKey}
```

**Path parameters**

| Name       | Type   | Required | Description                      |
| ---------- | ------ | -------- | -------------------------------- |
| merchantId | string | Yes      | The ID of the merchant.          |
| requestId  | string | Yes      | The ID of the request to reject. |

**Request body**

| Field             | Type   | Required | Description                                   |
| ----------------- | ------ | -------- | --------------------------------------------- |
| reason            | string | No       | Optional note recorded with the rejection.    |
| approverReference | string | No       | Optional external reference for the approver. |

#### Cancel a Request

Cancels a request before it has been approved or applied.

**Authentication**

```
X-Api-Key: {adminApiKey}
```

**Path parameters**

| Name       | Type   | Required | Description                      |
| ---------- | ------ | -------- | -------------------------------- |
| merchantId | string | Yes      | The ID of the merchant.          |
| requestId  | string | Yes      | The ID of the request to cancel. |

#### Get a Single Request

Returns one update request by ID.

**Authentication**

```
X-Api-Key: {adminApiKey}
```

**Path parameters**

| Name       | Type   | Required | Description             |
| ---------- | ------ | -------- | ----------------------- |
| merchantId | string | Yes      | The ID of the merchant. |
| requestId  | string | Yes      | The ID of the request.  |

#### 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.                                                           |
| SortBy        | string           | Field to sort by.                                                                     |
| SortDirection | string           | Sort direction: asc or desc.                                                          |

**Example request**

```bash
curl -X GET \
  "https://api.test.devs.beadpay.io/Merchants/{merchantId}/update-requests?Statuses=submitted&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 `justification` to record why a change was requested. This is the field the split funding enrollment example uses, and it is good practice for any change an approver did not originate.

Use `submit: false` when you want to stage a change for review before it enters the approval queue, then call the submit endpoint when it is ready.

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, except for split funding enrollment, which `expiresAt` does not revert. Submit an explicit `false` request to remove a merchant from the Split Funding Program. Setting `expiresAt` on every request also keeps a later request at the same `precedence` from being rejected with `409 Conflict`.

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

* [Get Merchant](/entity-management/merchant-management/get-merchant)
* [List Merchants](/entity-management/merchant-management/list-merchants)
* [Split Funding Program](/split-funding)


# 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

| Phase     | Typical call                                                             | What happens                                                                                                  |
| --------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| Provision | `POST /Merchants/{id}/locations`                                         | Returns a `locationId` used when creating terminals or pulling reporting data                                 |
| Configure | `PUT /Locations/{id}`                                                    | Replace all editable fields including name, tenderTypes, business type, MCC values, ticket sizes, and address |
| Disable   | `PUT /Locations/{id}/enabled`                                            | Stops new payment activity while preserving location history for reporting and settlement                     |
| Operate   | Use `locationId` across terminal creation, reporting, and reconciliation | Dashboards and settlement reports group transactions by location                                              |
| Retire    | `DELETE /Locations/{id}` once no active terminals remain                 | Permanently removes the location record                                                                       |

#### Hierarchy recap

```
Partner
└─ Merchant
   └─ Location
      └─ Terminal
         └─ Payment
```

#### Endpoints at a glance

| Action           | Method and path                  |
| ---------------- | -------------------------------- |
| Create location  | `POST /Merchants/{id}/locations` |
| List locations   | `GET /Merchants/{id}/locations`  |
| Get location     | `GET /Locations/{id}`            |
| Update location  | `PUT /Locations/{id}`            |
| Disable location | `PUT /Locations/{id}/enabled`    |
| Delete location  | `DELETE /Locations/{id}`         |

#### What boarding creates

{% hint style="warning" %}
When an onboarding application reaches `boarded`, Bead automatically creates a default **Location** and one or more default **Terminal(s)** for the merchant. You do not create these yourself. This section documents what Bead creates so you know what to look for. It does not cover manual location or terminal creation, which is unchanged and described elsewhere on this page.
{% endhint %}

Which records get created is driven entirely by the `locationBusinessType` value you submitted on the application:

| `locationBusinessType` | Default Location created      | Default Terminal(s) created                              |
| ---------------------- | ----------------------------- | -------------------------------------------------------- |
| `physical`             | One Location, type `physical` | One Terminal, `type: physical`                           |
| `virtual`              | One Location, type `virtual`  | One Terminal, `type: virtual`                            |
| `both`                 | One Location, type `both`     | Two Terminals: one `type: virtual`, one `type: physical` |

**Integrator sequence**

1. Wait for the application to reach `boarded` (via [Get Status](/onboarding/get-status) or the boarding webhook). The default Location and Terminal(s) exist at that point, so you can retrieve them immediately.
2. List the merchant's terminals ([List Terminals](/entity-management/terminal-management/list-terminals)) to identify the default terminal(s) Bead created.
3. Reset the API key on each default terminal you will use ([Reset Terminal API Key](/entity-management/terminal-management/reset-terminal-api-key)) and store the `apiKey.secret` from the reset response. List Terminals and Get Terminal return only the masked value, so reset is the only way to obtain the terminal payments API key.
4. Make the credentials available to the reseller.

A terminal Bead creates at boarding has not had its key delivered to anyone, so the first reset does not interrupt an integration. Resetting again later replaces the key immediately, so do it only when you are ready to update every system that uses the current key.

After boarding, you may create additional terminals or edit the defaults as needed. See [Terminal Management](/entity-management/terminal-management).

**Distinguishing default terminals**

There is no field that marks a terminal as Bead-created versus integrator-created. No `isDefault`, `source`, or similar flag exists on the terminal record. `type` (`virtual` or `physical`) is the only distinguishing attribute available.

For a `both`-type boarding, `type` is sufficient to tell the two default terminals apart from each other and to route each credential to the correct acceptance channel (virtual vs. physical). It is **not** sufficient to tell a default terminal apart from a same-type terminal you create later. If you create a second `physical` terminal on the same location, you must track which one is the original default yourself (for example, by recording the terminal `id` returned at the earliest post-boarding [List Terminals](/entity-management/terminal-management/list-terminals) call, or by naming or tagging terminals you create so they are distinguishable from Bead's defaults going forward).

#### Business types

The `locationBusinessType` field controls which payment connections are provisioned for the merchant during onboarding, and which default Location and Terminal(s) Bead creates at boarding (see [What boarding creates](#what-boarding-creates) above). **Set this value carefully. What matters is the value at boarding, not the value you first submitted.**

| Value      | Description                                            | Payment connections provisioned                                                                                                                                | Records created at boarding                                                             |
| ---------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `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. | One Location (type `physical`) and one Terminal (`type: physical`)                      |
| `virtual`  | Online or remote business. Requires a `website` value. | Virtual wallet connections only. Physical terminal connections are not built.                                                                                  | One Location (type `virtual`) and one Terminal (`type: virtual`)                        |
| `both`     | Operates both physically and online                    | Both physical and virtual connections are provisioned. Supports all terminal types and tender configurations.                                                  | One Location (type `both`) and two Terminals: one `type: virtual`, one `type: physical` |

{% hint style="info" %}
**`locationBusinessType` can still change while the application is in flight.** Before boarding, you can update the value on the application. Bead does not require a full resubmission just to correct it pre-boarding. What matters is the value at the moment the application boards, not the value you first submitted.

**After boarding is a different story.** If a merchant boards with `locationBusinessType: "physical"` (or `"virtual"`) and later needs to accept payments in the other environment (the common case is a merchant boarded `"virtual"` who later wants to take payments in store), adding that environment is account maintenance, not a resubmission. It will likely trigger a new compliance review scoped to the added environment. That review cycle is the real cost of getting this wrong at boarding.
{% endhint %}

**Don't default to `"both"` to sidestep this.** Selecting `"both"` when a merchant only needs one environment is not a safe hedge. It attracts more scrutiny at compliance review than a scoped, correct selection. Set the value to match what the merchant actually needs at boarding.

#### 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, and which default Location and Terminal(s) are created. 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.

**Don't recreate what boarding already created.** Check for the default Location and Terminal(s) before provisioning new ones. See [What boarding creates](#what-boarding-creates). Creating a duplicate terminal after boarding leaves two valid terminals on the location and creates ambiguity about which credentials the reseller should use.

**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)
* [Terminal Management](/entity-management/terminal-management)
* [List Terminals](/entity-management/terminal-management/list-terminals)


# 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.

{% hint style="warning" %}
**Boarding creates the location and terminals for you.** When a merchant application is boarded through [Onboarding](/onboarding), Bead creates the merchant, its location, and its terminals as part of boarding. Do not create your own location or terminal for a merchant that is still in onboarding, or before the application reaches `boarded`. Use this endpoint to add further locations to a merchant that has already been boarded. Use Get Status to confirm the application status and to retrieve `onboardedMerchantId`.
{% endhint %}

#### 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`. See Business type values below                        |
| `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.

**Business type values**

| Value      | Use when                                                                 |
| ---------- | ------------------------------------------------------------------------ |
| `physical` | The location takes payments in person only. Send `website` as `null`.    |
| `virtual`  | The location takes payments online only. `website` is required.          |
| `both`     | The location takes payments in person and online. `website` is required. |

These are the only accepted values. Send them in lowercase exactly as shown.

**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 Status](/onboarding/get-status)


# 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.

{% hint style="warning" %}
**Boarding creates the location and terminals for you.** When a merchant application is boarded through [Onboarding](/onboarding), Bead creates the merchant, its location, and its terminals as part of boarding. Do not create your own location or terminal for a merchant that is still in onboarding, or before the application reaches `boarded`. Use [Create Terminal](/entity-management/terminal-management/create-terminal) to add further terminals to a merchant that has already been boarded.
{% endhint %}

#### How records are organized

Each partner is its own ISO instance. There is no root above it that spans partners, so one partner cannot see or manage another partner's records. Any partner tiers sit beneath that ISO. Under a partner, a merchant owns its locations, and a location owns its terminals. See [Advanced Partner Hierarchy](/reference-guide/core-concepts/advanced-partner-hierarchy) for how tiers are structured.

#### 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
* Retrieve or reset a terminal's payments API key
* 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}`
* **Retrieve payments API key:** `GET /Terminals/{id}/api-keys`
* **Reset payments API key:** `POST /Terminals/{id}/api-keys`
* **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. Confirm the merchant has been boarded and identify its location. Boarding creates the first location and terminals.
2. If you need another terminal, create it 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 is boarded and the location is 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.

#### Terminal payments API key

Each terminal has its own payments API key. Your backend sends it in the `X-Api-Key` header to create payments and read payment status for that terminal. The full key is returned as `apiKey.secret` when the terminal is created. To get it again, call `GET /Terminals/{id}/api-keys`. To replace it, call `POST /Terminals/{id}/api-keys`. See [Reset Terminal API Key](/entity-management/terminal-management/reset-terminal-api-key) for both calls.

#### 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

See [Webhook Management](/entity-management/terminal-management/webhook-management) for setup details.

#### 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.

#### Terminal type: physical vs. virtual

Every terminal is created with a `type` of either `virtual` or `physical` (see [Create Terminal](/entity-management/terminal-management/create-terminal)). These are not interchangeable labels — they represent genuinely different acceptance environments, and Bead applies different standing tender-type policy to each:

| Type       | Acceptance environment                                                                                                 | Standing tender default                                                                                                                                                        |
| ---------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `virtual`  | Online or remote acceptance — hosted checkout, eCommerce, invoicing, or any flow with no customer physically present.  | All approved tender types enabled by default.                                                                                                                                  |
| `physical` | In-person acceptance — point-of-sale or service counter, where a customer is typically physically present and waiting. | BTC (on-chain) disabled by default. On-chain confirmation is long-running and generally unsuitable for a customer waiting at a counter; other approved tenders remain enabled. |

Because these two policies cannot coexist on a single terminal, `type` is not cosmetic — it is set once at creation (see Create Terminal) and cannot be changed later. See [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment) for the full framework on matching tender types to acceptance environment, and [Managing Tender Types](/entity-management/terminal-management/managing-tender-types) for how the approved tender set flows down to a terminal.

Terminal type also shapes the consumer experience. On a `physical` terminal, the hosted payment page presents each tender as a QR code that the consumer scans and completes on their own phone. For what to consider when building an in-person checkout, see [In-Person Payments (Physical Terminals)](/reference-guide/payment-flows/in-person-payments-physical-terminals).

**Multi-terminal merchants.** A merchant that accepts payments in more than one environment needs a separate terminal per environment — a merchant with both physical point-of-sale and online checkout needs at least one `physical` terminal and one `virtual` terminal. It is common for a single merchant to have several terminals across locations, channels, and use cases.

#### Phases

**1. Create**

Create a terminal with:

* `POST /Terminals`

Provide the required terminal details in the request body, including `locationId` and `type`.

Use this phase when you are provisioning a terminal for a location and preparing it for operational use. Decide `type` (`virtual` or `physical`) at this point — see [Terminal Lifecycle (Concepts)](/entity-management/terminal-management/terminal-lifecycle-concepts#terminal-type-physical-vs-virtual) above — since it cannot be changed after creation.

**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. `type` cannot be changed via this endpoint — create a new terminal if a different type is needed.

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`, choosing `type` deliberately for the acceptance environment it will serve
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`, choosing `type` (`virtual` or `physical`) deliberately since it is permanent, 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.

{% hint style="warning" %}
**Boarding creates the location and terminals for you.** When a merchant application is boarded through [Onboarding](/onboarding), Bead creates the merchant, its location, and its terminals as part of boarding. Do not create your own terminal for a merchant that is still in onboarding, or before the application reaches `boarded`. Use this endpoint to add further terminals, for example one per acceptance environment, to a merchant that has already been boarded. Use [Get Status](/onboarding/get-status) to confirm the application status and to retrieve `onboardedMerchantId`.
{% endhint %}

**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. One of `virtual` or `physical` — see below. Set at creation and cannot be changed afterward.                             |
| `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.                                                                                  |

**`type` values**

| Value      | Description                                                                                                                                                       |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `virtual`  | An online or remote acceptance environment — hosted checkout, eCommerce, invoicing, or any flow where the customer is not physically present at a counter.        |
| `physical` | An in-person acceptance environment, such as a point-of-sale or service counter, where a customer is physically present and typically waiting on the transaction. |

`type` is not cosmetic. It is the primary signal Bead uses for the **standing tender-type recommendation** applied to the terminal: virtual terminals default to all approved tenders enabled, while physical terminals default to BTC (on-chain) **disabled**, because on-chain confirmation is long-running and unsuitable for a customer waiting at a counter. See [Terminal Lifecycle (Concepts)](/entity-management/terminal-management/terminal-lifecycle-concepts) for the full explanation, and [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment) for how to select tenders per environment.

`type` is set once at creation and cannot be changed later — [Update Terminal](/entity-management/terminal-management/update-terminal) does not accept it. If a terminal needs a different `type`, create a new terminal rather than attempting to convert an existing one.

**A merchant accepting payments in more than one environment needs a separate terminal per environment.** A merchant with both a physical point-of-sale and an online checkout needs at minimum one `physical` terminal and one `virtual` terminal — a single terminal cannot represent both. It is common for one merchant to have several terminals across locations and channels; see [Managing Tender Types](/entity-management/terminal-management/managing-tender-types) for how the approved tender set flows down to each one.

**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**

The create response is where Bead returns the terminal payments API key for a terminal you create. Store these values securely right away:

* `id` — the terminal ID
* `apiKey.secret` — the terminal payments API key

Send the terminal payments API key in the `X-Api-Key` header on payment requests for that terminal. Terminal payments authenticate with the API key header only.

**Getting the terminal payments API key again**

Bead does not return the usable key again after the terminal is created. [Get Terminal](/entity-management/terminal-management/get-terminal) and [List Terminals](/entity-management/terminal-management/list-terminals) return only the masked value.

If you did not capture the key when you created the terminal, or you need the key for a terminal Bead created at boarding, issue a new key with `POST /Terminals/{id}/api-keys`. The reset response is the only place the new key is returned, and resetting replaces the previous key immediately. Read [Reset Terminal API Key](/entity-management/terminal-management/reset-terminal-api-key) before you call it.

**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)
* [Reset Terminal API Key](/entity-management/terminal-management/reset-terminal-api-key)
* [Terminal Lifecycle (Concepts)](/entity-management/terminal-management/terminal-lifecycle-concepts)
* [Managing Tender Types](/entity-management/terminal-management/managing-tender-types)
* [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment)


# 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": null,
      "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. `secret` is not returned in a listing and `maskedSecret` is a display value only. |
| `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.
* This endpoint does not return the terminal payments API key. To obtain a usable key for a terminal, including the default terminals Bead creates at boarding, use [Reset Terminal API Key](/entity-management/terminal-management/reset-terminal-api-key).
* 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": null,
    "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: `virtual` or `physical`. Set at creation and immutable — see [Terminal Lifecycle (Concepts)](/entity-management/terminal-management/terminal-lifecycle-concepts#terminal-type-physical-vs-virtual).                           |
| `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. On this endpoint `secret` is not returned and `maskedSecret` is a display value only. To obtain a usable key, see [Reset Terminal API Key](/entity-management/terminal-management/reset-terminal-api-key). |
| `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.
* This endpoint does not return the terminal payments API key. The only way to obtain a usable key after creation is [Reset Terminal API Key](/entity-management/terminal-management/reset-terminal-api-key).

#### 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)
* [Terminal Lifecycle (Concepts)](/entity-management/terminal-management/terminal-lifecycle-concepts)


# Reset Terminal API Key

Issues a new payments API key for an existing terminal. This is the only way to obtain a usable terminal payments API key after the terminal has been created, including for the default terminals Bead creates at boarding. The call returns the updated terminal record with the new key.

Use the reset call when:

* you need the key for a terminal Bead created at boarding
* you did not capture the key when you created a terminal
* you need to replace a suspected exposed key

{% hint style="warning" %}
**Resetting replaces the previous key immediately.** There is no overlap window. Any system still using the old key receives `401 Unauthorized` as soon as the reset completes.

* Reset only when no system depends on the current key, or when you are ready to update every system that does.
* The reset response is the only place the new key is returned. Store `apiKey.secret` before you do anything else with it.
* Do not blindly retry a reset after a timeout. A second reset invalidates the key from the first one. Confirm the outcome of the first call before you reset again.
  {% endhint %}

### Endpoint

| Action                     | Endpoint                                                        |
| -------------------------- | --------------------------------------------------------------- |
| Reset the terminal API key | `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](/entity-management/terminal-management/create-terminal), [List Terminals](/entity-management/terminal-management/list-terminals), or [Get Terminal](/entity-management/terminal-management/get-terminal). |

### Request

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.

### Read endpoints do not return the key

[Get Terminal](/entity-management/terminal-management/get-terminal) and [List Terminals](/entity-management/terminal-management/list-terminals) return only `apiKey.maskedSecret`. Do not build a flow that expects to read the usable key back from a terminal record. The same applies to `GET /Terminals/{id}/api-keys`, which is listed in the API reference but does not return a usable key to API key callers. To obtain a key, use the reset call on this page.

### 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 reset.        |

### Notes

* Use your **admin API key** for these terminal management operations.
* Use the 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.
* Resetting replaces the previous key, so update every system that uses the old key.

### Response codes for reset

| 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**

<table><thead><tr><th width="159">Header</th><th>Value</th></tr></thead><tbody><tr><td><code>X-Api-Key</code></td><td><code>{adminApiKey}</code></td></tr><tr><td><code>Content-Type</code></td><td><code>application/json</code></td></tr><tr><td><code>Accept</code></td><td><code>application/json</code></td></tr></tbody></table>

**Path parameter**

<table><thead><tr><th width="113">Name</th><th width="131">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>string</td><td>Terminal identifier returned by Create Terminal, List Terminals, or Get Terminal.</td></tr></tbody></table>

**Request body**

<table><thead><tr><th width="115">Field</th><th width="143">Type</th><th width="112">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>url</code></td><td>string (URI)</td><td>Yes</td><td>Fully qualified HTTPS URL where terminal-level payment status events should be delivered. Maximum 512 characters.</td></tr></tbody></table>

**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**

<table><thead><tr><th width="164">Code</th><th>Meaning</th></tr></thead><tbody><tr><td><code>200</code></td><td>Webhook URL set or updated successfully.</td></tr><tr><td><code>202</code></td><td>Request accepted for processing.</td></tr><tr><td><code>204</code></td><td>Request completed with no response body.</td></tr></tbody></table>

**Error responses**

<table><thead><tr><th width="166">Code</th><th>Meaning</th></tr></thead><tbody><tr><td><code>400</code></td><td>The request body is invalid, such as a missing or malformed URL.</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 permitted to manage the specified terminal, or a terminal payments API key was used instead of an admin API key.</td></tr><tr><td><code>404</code></td><td>No terminal was found for the supplied <code>id</code>.</td></tr><tr><td><code>409</code></td><td>A conflicting terminal state prevented the update.</td></tr></tbody></table>

**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**

<table><thead><tr><th width="193">Header</th><th>Value</th></tr></thead><tbody><tr><td><code>X-Api-Key</code></td><td><code>{adminApiKey}</code></td></tr><tr><td><code>Accept</code></td><td><code>application/json</code></td></tr></tbody></table>

**Path parameter**

<table><thead><tr><th width="127">Name</th><th width="132">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>string</td><td>Terminal identifier returned by Create Terminal, List Terminals, or Get Terminal.</td></tr></tbody></table>

**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**

<table><thead><tr><th width="146">Code</th><th>Meaning</th></tr></thead><tbody><tr><td><code>400</code></td><td>The request is invalid.</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 permitted to manage the specified terminal.</td></tr><tr><td><code>404</code></td><td>No terminal was found for the supplied <code>id</code>.</td></tr></tbody></table>

**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.

`x-webhook-signature` is included on **both** terminal-level webhook deliveries and per-payment deliveries sent to `webhookUrls` — both use the same terminal's `signingSecret` and the same signing logic. Each individual delivery carries its own `t` (timestamp) generated at send time, so the resulting `s` (signature) value is different per delivery even when the JSON body is identical across recipients. Your verification code doesn't need to change based on which delivery path an event arrived on.

**Important — signing depends on the terminal having a secret configured.** A terminal only has a `signingSecret` once you've called `PUT /Terminals/{id}/webhook` on it at least once. If you only ever use `webhookUrls` and have never set a terminal-level default webhook, that terminal has no `signingSecret`, and its `webhookUrls` deliveries are sent **without** an `x-webhook-signature` header — silently, with no error or warning. If you plan to rely primarily on `webhookUrls` for routing, set a terminal-level default webhook first (even one you don't actively use) specifically to establish the `signingSecret`, then verify all deliveries — terminal-level and per-payment — using that secret.

**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.
* Set a terminal-level webhook (to establish a `signingSecret`) even if `webhookUrls` is your primary delivery mechanism — otherwise your deliveries may be unsigned with no indication anything is missing.

**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.

If you're building a checkout or POS surface and need to decide which tender buttons to show a consumer at runtime, see [Reading Tender Availability at Runtime](/reference-guide/payment-flows/reading-tender-availability-at-runtime) — that page covers polling cadence, caching, and graceful failure. This page covers configuration and boarding.

#### 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 you're deciding what to render for a consumer at checkout rather than diagnosing a configuration issue, see [Reading Tender Availability at Runtime](/reference-guide/payment-flows/reading-tender-availability-at-runtime) for the recommended read/cache pattern.

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. A terminal's tender set is not fixed at boarding time — see [Reading Tender Availability at Runtime](/reference-guide/payment-flows/reading-tender-availability-at-runtime) for how a checkout or POS integration should handle that change without a code deployment.

#### 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. See [Reading Tender Availability at Runtime](/reference-guide/payment-flows/reading-tender-availability-at-runtime) for how a checkout or POS integration should pick this up without hardcoding a fixed tender list.

#### 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.
7. For checkout or POS surfaces that need to decide what to render for a consumer at runtime, follow the read-and-cache pattern in [Reading Tender Availability at Runtime](/reference-guide/payment-flows/reading-tender-availability-at-runtime) rather than hardcoding a tender list.


# 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.
* Identify the batch and settlement a payment belongs to, and the state of that batch.
* 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 the settlement linkage fields on each PaymentResponse to identify the batch and settlement the payment belongs to.
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, total fees, adjustments, net adjustment total, 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 exactly four settlement linkage fields: `settlementId`, `batchId`, `batchPeriod`, and `batchStatus`. `settlementId` and `batchId` identify the settlement and batch the payment belongs to, `batchPeriod` is the date of the batch period, and `batchStatus` is the state of the batch (`open`, `closed`, or `hold`). These fields populate as the payment moves through batching and settlement.

`batchStatus` is a property of the batch, not of the individual payment. There is no payment-level settlement status field and no timestamp recording when a payment was included in a settlement. To reason about settlement state, read the settlement record itself using `GET /Settlements/{id}`, whose `status` carries the settlement state (`cleared`, `hold`, `dispatched`, or `paid`).

> **Note:** Not every payment endpoint returns the same schema. `GET /Merchants/{id}/payments` and `GET /Partners/{id}/payments` return PaymentResponse, which carries all four linkage fields. `GET /Terminals/{id}/payments` returns a different schema, PaymentInfo, which carries `settlementId`, `batchId`, and `batchStatus` but does **not** carry `batchPeriod`. If your reconciliation logic depends on `batchPeriod`, source payments from the merchant or partner endpoint.

**Payment settlement detail**

Payment settlement detail explains how individual payments connect to settlement activity, including which batch they belong to and which settlement covers them.

Use payment settlement detail when you need to understand how an individual payment settled, which batch it belongs to, and the state of that batch. 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/{id}/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 its `status`, fee totals, and net fundable amount. The settlement record, not the payment, is where settlement state lives.
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`, `totalFees`, `adjustments`, `netAdjustmentTotal`, and `netFundableAmount` on each settlement. `totalFees` is the settlement's rolled-up fee total and `netAdjustmentTotal` is the net of all adjustments applied, so both are needed to follow the gross-to-net arithmetic without recomputing it yourself.
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 see which settlement and batch cover the payment and what state that batch is in.
3. Use `GET /Settlements/{id}` for the settlement's own `status` and `settlementDate`, and `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 [Settlement Status Codes](/reference-guide/enumerations-and-schemas/settlement-status-codes) when interpreting settlement states.
* Review Settlement currencies in the [Settlement Currencies](/reference-guide/enumerations-and-schemas/settlement-currencies) 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.

> **Split Funding Program merchants:** the fields below still apply, but with different inputs. On a split funding payment, the per-transaction fee is zero and the gross contribution to the settlement is `merchantPortion`, not the full amount the consumer paid. See [Split funding fields](#split-funding-fields) below, and do not use settlement-level split funding totals as the source of truth for a partner distribution. See the caution there.

**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.
* The number of batches included in the period, as `batchCount`.
* Total transaction fees withheld across those batches.
* A deposit fee for the ACH or wire transfer used to fund the merchant.
* A rolled-up fee total for the settlement, as `totalFees`.
* Any adjustments (chargebacks, refund recoveries, platform fees, holdbacks, and others), plus their signed sum as `netAdjustmentTotal`.
* 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.                                           |

`GET /Settlements/{id}` accepts no query parameters. Only the path `id` is required.

**Query parameters**

**GET /Settlements and GET /Merchants/{merchantId}/settlements**

| Parameter     | Type             | Description                                                                                                             |
| ------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------- |
| From          | date             | Start of the settlement created-date window (UTC). Filters on the settlement's `created` date, not on `settlementDate`. |
| To            | date             | End of the settlement created-date window (UTC). Filters on the settlement's `created` date, not on `settlementDate`.   |
| 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. Functions on GET /Settlements only. See the callout below.                          |
| Page          | integer          | Zero-based page number. Defaults to 0.                                                                                  |
| PageSize      | integer          | Results per page. Defaults to 50, maximum 100.                                                                          |
| SortBy        | string           | Field to sort by. See Sortable fields.                                                                                  |
| SortDirection | string           | Sort direction: asc or desc.                                                                                            |

> **`MerchantIds` has no effect on `GET /Merchants/{merchantId}/settlements`.** The parameter is accepted and returns HTTP 200, but it does not change the result set: passing a different merchant's ID returns the same records as the unfiltered request, all still belonging to the merchant in the path. Confirmed by direct testing. The path parameter is the only merchant scope on that endpoint. Use `GET /Settlements` with `MerchantIds` when you want to query several merchants in one call, which is also the more efficient pattern for portfolio reconciliation. See [Settlement Reconciliation Patterns](/settlement/settlement-reconciliation-patterns).

> **Date format:** `From` and `To` accept date-only values (`2026-06-22`). They are date-only parameters: a full ISO date-time with a time component, including an ISO date-time with a `.000Z` suffix, fails validation. Confirmed on `GET /Settlements` and `GET /Batches` by direct testing.

> **`To` appears to be exclusive.** A same-day window (`From` and `To` set to the same date) returns zero records, while extending `To` by one day returns the expected record. Extend `To` past the last day you want included. This boundary behavior was observed in testing but has not been formally confirmed with engineering.

> **What the date window filters:** `From` and `To` bound the settlement's created-date window in UTC. They do not filter on `settlementDate`. If you need to report by actual settlement date, retrieve records by created-date window and then filter or sort on `settlementDate` yourself.

**GET /Settlements/{id}/batches**

| Parameter     | Type             | Description                                                                                                                                                                                       |
| ------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| From          | date             | Start of the date-only window (UTC).                                                                                                                                                              |
| To            | date             | End of the date-only window (UTC).                                                                                                                                                                |
| Status        | string           | Filter by batch status: open, closed, or hold.                                                                                                                                                    |
| Type          | string           | Filter by currency category: crypto, walletApps, or fiat.                                                                                                                                         |
| MerchantIds   | array of strings | Filter by one or more merchant IDs. Not verified as functional on this path-scoped endpoint.                                                                                                      |
| SettlementIds | array of strings | Filter by one or more settlement IDs. Intersects with the path scope, it does not override it. Passing a different settlement's ID returns an empty result rather than that settlement's batches. |
| Page          | integer          | Zero-based 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.                                                                                                                                                                      |

> **`SettlementIds` intersects, it does not override.** On `GET /Settlements/{id}/batches` the path parameter always constrains the result. Supplying `SettlementIds` for a different settlement returns an empty `data` array, not the other settlement's batches. Confirmed by direct testing. To read another settlement's batches, change the path.

**GET /Settlements/{id}/deposits**

| Parameter     | Type             | Description                                                                                    |
| ------------- | ---------------- | ---------------------------------------------------------------------------------------------- |
| From          | date             | Start of the date-only window (UTC).                                                           |
| To            | date             | End of the date-only window (UTC).                                                             |
| Status        | string           | Filter by deposit status: dispatched, paid, or error.                                          |
| Method        | string           | Filter by bank deposit method: ach or wire.                                                    |
| MerchantIds   | array of strings | Filter by one or more merchant IDs. Not verified as functional on this path-scoped endpoint.   |
| SettlementIds | array of strings | Filter by one or more settlement IDs. Not verified as functional on this path-scoped endpoint. |
| Page          | integer          | Zero-based 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.                                                                   |

**Sortable fields**

For `GET /Settlements`, `SortBy` accepts exactly these values:

| Value                | Sorts on                                  |
| -------------------- | ----------------------------------------- |
| type                 | Settlement currency category.             |
| status               | Current settlement state.                 |
| batchCount           | Number of batches in the settlement.      |
| grossFundableAmount  | Gross payment volume.                     |
| totalTransactionFees | Period total of per-transaction fees.     |
| depositFees          | Funding transfer fee.                     |
| totalFees            | Rolled-up fee total for the settlement.   |
| netAdjustmentTotal   | Signed sum of adjustments.                |
| netFundableAmount    | Net amount funded to the merchant.        |
| settlementDate       | Actual settlement date, when present.     |
| created              | Settlement record creation timestamp.     |
| updated              | Settlement record last-updated timestamp. |

**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.                                                  |
| holdReason           | string or null      | Reason the settlement is on hold. Null unless `status` is hold.                                  |
| batchIds             | array of strings    | Batch IDs included in this settlement.                                                           |
| batchCount           | int32               | Number of batches included in this settlement.                                                   |
| payoutId             | string or null      | Payout reference associated with this settlement, when applicable.                               |
| grossFundableAmount  | double              | Total payment volume before fees and adjustments.                                                |
| totalTransactionFees | double              | Sum of all per-transaction fees withheld across the batches in this settlement period.           |
| depositFees          | double              | Fee for the ACH or wire transfer used to fund this settlement.                                   |
| totalFees            | double              | Rolled-up fee total recorded on this settlement.                                                 |
| adjustments          | array of Adjustment | List of adjustments applied to this settlement. See Adjustments.                                 |
| netAdjustmentTotal   | double              | Signed sum of the `netAdjustment` values in `adjustments`. Negative reduces net fundable amount. |
| netFundableAmount    | double              | 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 + netAdjustmentTotal`

Every term in that expression is a first-class field on the settlement record. `netAdjustmentTotal` is signed: negative adjustments such as chargebacks reduce the net, and credits such as `adjustmentCredit` increase it.

`totalFees` is the settlement's own rolled-up fee total. Use it when you want a single fee figure for the period rather than the separate `totalTransactionFees` and `depositFees` components, and reconcile it against your own ledger before treating it as interchangeable with their sum.

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.

#### Split funding fields

For a merchant enrolled in the [Split Funding Program](/split-funding), a split funding payment settles at `merchantPortion` with a transaction fee of zero. The fee table is bypassed entirely for that payment. `grossFundableAmount` and `totalTransactionFees` on the settlement reflect that: the gross contribution from a split funding payment is `merchantPortion`, not the amount the consumer paid, and there is no transaction fee to withhold from it.

The deposit fee depends on the composition of the settlement. On a settlement made up entirely of split funding payments, the merchant's deposit is funded at full net with no fee deducted and the fee is recovered from the partner portion instead. On a settlement that mixes split funding and ordinary payments, the deposit is charged as usual and nothing is recovered from the partner portion. See [Settlement behavior](/split-funding#settlement-behavior).

Two informational fields report the split funding activity in a settlement period:

| Field                        | Type    | Description                                                                                                                    |
| ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------ |
| partnerPortion               | double  | Sum of the partner portion across split funding payments in the settlement. Zero for merchants with no split funding activity. |
| splitFundingTransactionCount | integer | Number of split funding payments in the settlement. Zero for merchants with no split funding activity.                         |

**Both fields sit on the settlement report rows, not on the settlement record.** The settlement responses returned by the endpoints on this page do not carry either field, so read them from the settlement report rather than from the record.

The partner portion total and the split funding payment count are informational aggregates for visibility. They tell you how much split funding activity happened in the settlement, but they are **not** the basis for what a partner is owed:

> Distributions to the partner are assessed from payments directly, not from settlement-level totals, because refunded and partially refunded payments must be excluded from the payout and that exclusion happens at the payment level. Do not build a partner distribution calculation from the settlement record. Use [Split Funding Reports](/split-funding/reports), specifically the distribution summary, as the source of truth for a partner distribution.

**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.                         |

`status` is returned as a plain string in the schema. Treat these four values as the set it carries, and read `holdReason` whenever `status` is hold.

**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 | double         | 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. Use `netAdjustmentTotal` when you need the period's signed adjustment total without iterating the array.

**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`, `totalFees`, `adjustments`, `netAdjustmentTotal`, 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` and `batchCount` to retrieve and count 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`. On a settlement made up entirely of split funding payments, expect no deposit fee on the merchant's side.

**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.
* If the merchant is on the Split Funding Program, use [Split Funding Reports](/split-funding/reports) for partner distribution reconciliation, not this page's settlement totals.


# 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.

> **Building a report for a Split Funding Program partner?** The settlement-level fields on this page cover standard processing residuals. They are not the source of truth for a split funding partner distribution. See [Split Funding Program merchants](#split-funding-program-merchants) below.

**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, and others), along with `netAdjustmentTotal`, the signed sum of those adjustments.
5. Bead funds the merchant the `netFundableAmount`, which is gross minus all fees plus the signed adjustment total.
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                                                                                                                                                                                            |
| Rolled-up fee total             | SettlementResponse                            | `totalFees`                    | The settlement's own single fee total for the period                                                                                                                                                                                     |
| Adjustments                     | SettlementResponse                            | `adjustments[]`                | Chargebacks, refunds, platform fees, holdbacks, and others                                                                                                                                                                               |
| Adjustments (signed total)      | SettlementResponse                            | `netAdjustmentTotal`           | Signed sum of `netAdjustment` across `adjustments[]`                                                                                                                                                                                     |
| Batch count for the period      | SettlementResponse                            | `batchCount`                   | Number of batches rolled into the settlement                                                                                                                                                                                             |
| Net payout to merchant          | SettlementResponse                            | `netFundableAmount`            | What the merchant received after all deductions                                                                                                                                                                                          |
| Partner portion (informational) | Settlement report row (`SettlementReportRow`) | `partnerPortion`               | Sum of the partner portion across the settlement's split funding payments; not part of the payable math. Not the distribution source of truth. See below. This field is not on `SettlementResponse`; read it from the settlement report. |
| Split funding payment count     | Settlement report row (`SettlementReportRow`) | `splitFundingTransactionCount` | Number of split funding payments in the settlement. Not on `SettlementResponse`; read it from the settlement report.                                                                                                                     |

**Transaction fees are not tracked at the individual payment level for standard processing.** 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.

`From` and `To` bound the settlement created-date window in UTC. They filter on the settlement's `created` date, not on `settlementDate`, and they are date-only values (`2026-06-01`). A full ISO timestamp with a time component fails validation. If your report needs to be organized by actual settlement date, retrieve by created-date window and then group on `settlementDate` yourself.

**Step 2: Retrieve settlements for the period**

```
GET /Settlements?MerchantIds[]=<merchantId>&From=<startDate>&To=<endDate>
```

Retrieve one page at a time. `Page` is zero-based and defaults to 0. `PageSize` defaults to 50 and accepts up to 100. 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`
* `totalFees`
* `adjustments`, iterating the array and noting type and amount for each
* `netAdjustmentTotal`
* `netFundableAmount`
* `settlementDate` (nullable; populated once the settlement has an actual settlement date)
* `status`, filtered to `dispatched` or `paid` to include only funded settlements

There is no `targetPayoutDate` field on `SettlementResponse`. The only date fields on the record are `settlementDate`, `created`, and `updated`. Use `settlementDate` for the actual settlement date and `created` for the window the `From` and `To` filters act on.

**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,369.75      |
| S-002      | $18,000 | $432             | $0.25       | none               | $17,567.75      |
| S-003      | $12,000 | $288             | $0.25       | +$50 (credit)      | $11,761.75      |

Each row is gross minus transaction fees minus the deposit fee plus the signed adjustment total:

* S-001: 20,000 - 480 - 0.25 - 150 = 19,369.75
* S-002: 18,000 - 432 - 0.25 = 17,567.75
* S-003: 12,000 - 288 - 0.25 + 50 = 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: $48,699.25

The period total reconciles both ways: 50,000 - 1,200 - 0.75 - 100 = 48,699.25, and 19,369.75 + 17,567.75 + 11,761.75 = 48,699.25.

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. `Page` is zero-based and defaults to 0, and `PageSize` defaults to 50, so a request that does not set `PageSize` returns 50 records per page rather than 100. 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. When a settlement is in `hold`, read `holdReason` for the reason it is held.

**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.

#### Split Funding Program merchants

A merchant enrolled in the [Split Funding Program](/split-funding) settles split funding payments at `merchantPortion` with no transaction fee, and the withheld difference is paid to the partner separately, on its own cadence, net of Bead's fee. This is a fundamentally different revenue flow from standard residual reporting, and the two should not be blended in the same calculation.

> **Split funding payments do not generate residuals.** Residual reporting derives partner revenue from fees Bead withheld from the merchant. On the Split Funding Program the merchant is charged nothing, so there is no fee to derive a residual from, and the partner's margin has already been paid to them through the distribution. A partner cannot be paid a residual and a split funding distribution on the same payment. If a partner moves traffic onto the program, residual revenue on that traffic stops and is replaced by the distribution. Build residual reports from non-split-funding payments only.

> **Expect the distribution to differ from the residual it replaces.** A residual derives from fees Bead charged the merchant. A split funding distribution is the consumer fee the partner collected, less Bead's fee on the merchant portion and the settlement fees. They are calculated from entirely different things and will not be the same size. Do not model a distribution as equal to the residual the same traffic would have produced. See [Bead's split funding fee](/split-funding#beads-split-funding-fee).

The partner portion total and the split funding payment count on the settlement report are informational aggregates. They tell you split funding activity happened, not what the partner is owed. They are computed at the settlement report level, but the actual partner distribution is assessed from payments directly. This matters once refunds apply: no Bead tender currently supports a refund, but the distribution logic is already built so that a refunded or partially refunded payment would be excluded from the payout at the payment level, not the settlement level, whenever that becomes possible.

**Do not use the settlement-level partner portion as a partner residual figure.** Use [Split Funding Reports](/split-funding/reports) instead:

* The split funding transaction report gives per-payment detail.
* The distribution summary gives the authoritative per-merchant, per-MID reconciliation, including Bead's fee and partner net. This is what was actually paid.

**Next steps**

* Use [Settlements](/settlement/settlements) 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 [Payment Settlement Detail](/settlement/payment-settlement-detail) for the merchant-level settlement event view.
* Use Reporting APIs when you need the underlying payment activity for a period.
* Use [Split Funding Reports](/split-funding/reports) for Split Funding Program partner distributions. Do not use this page's settlement totals for that purpose.
* 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 [Settlements](/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.                                                          |
| batchPeriod         | date              | Date associated with the batch period.                                                             |
| settlementId        | string or null    | Settlement associated with the batch, when assigned.                                               |

**Split funding fields**

For a merchant enrolled in the [Split Funding Program](/split-funding), two informational fields report the split funding activity in a batch:

| Field                        | Type              | Description                                                                          |
| ---------------------------- | ----------------- | ------------------------------------------------------------------------------------ |
| partnerPortion               | number or string  | Sum of the partner portion across split funding payments in the batch. Zero if none. |
| splitFundingTransactionCount | integer or string | Number of split funding payments in the batch. Zero if none.                         |

**Both fields sit on the batch report rows, not on the batch record.** The batch responses returned by the endpoints on this page do not carry either field, so read them from the batch report rather than from the record. They are informational aggregates for visibility and are not the basis for a partner distribution. See [Settlements](/settlement/settlements) and [Split Funding Reports](/split-funding/reports).

**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.

A split funding payment contributes `merchantPortion` to the batch's `grossFundableAmount` with a transaction fee of zero. See [Split Funding Program](/split-funding).

**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.
* If the merchant is on the Split Funding Program, see [Split Funding Program](/split-funding) for how the split funding aggregates are produced, and [Split Funding Reports](/split-funding/reports) for partner distribution reconciliation.


# 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](https://developers.bead.xyz/reporting) first. For the settlement-level fee and net amount, use Settlements. For payment-level settlement detail, use 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.

#### When a deposit appears

A deposit record does not exist until the payout for the settlement is dispatched. A settlement is created earlier in the day than its payout is sent, so there is a window, usually the morning and early afternoon Eastern Time, in which a new settlement has no deposit yet.

During that window, `GET /Settlements/{id}/deposits` returns a successful response with an empty list. The empty list does not mean the settlement was skipped, failed, or left unfunded. It means the payout has not been dispatched yet. The endpoint does not return a separate "not yet dispatched" status.

The same is true of `GET /Deposits` with the `SettlementIds` filter.

A typical day on a given settlement looks like this:

1. The settlement record is created.
2. The payout is dispatched later in the day, and the deposit record is created with status `dispatched`.
3. The deposit moves to `paid` and the settlement moves to `paid` once the bank movement is confirmed, usually the same afternoon or evening.

Exact times can change, so do not hardcode a schedule.

When you design a sync job:

* Do not treat an empty deposit list as a final answer. Treat it as "not yet available" and read again later.
* Prefer to read deposits only after the settlement `status` is `paid`, instead of reading at a fixed time of day.
* If you read on a schedule, run the job late in the day Eastern Time or the following morning, and re-check any settlement that has no deposit yet.
* Do not mark a settlement as missing a deposit until the settlement is `paid` and a later read still returns no deposit. If that happens, contact Bead support with the `settlementId`.

#### More than one deposit per settlement

A settlement can have more than one deposit record. If a deposit ends in `error`, a later deposit for the same settlement can follow it. The earlier record keeps its `error` status, and the later record has its own `id`, `status`, and `dispatchedDate`. For example, a single settlement can show one deposit with status `error` and a second deposit with status `paid`.

When you reconcile:

* Retrieve all deposits for the settlement with `GET /Settlements/{id}/deposits`, not only the first record.
* Treat the settlement as funded when it has a deposit with status `paid`. An earlier `error` record does not by itself mean the settlement was left unfunded.
* Expect more than one record per `settlementId` when you list deposits with `GET /Deposits` using the `SettlementIds` filter.

The API does not currently return a failure reason for a deposit with status `error`. If you need the cause, contact Bead support with the deposit `id` and `settlementId`.

#### 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. Returns an empty list until the payout is dispatched.         |

#### 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. A later deposit for the same settlement can follow it. See the section above. |

#### 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. Wait until the settlement `status` is `paid`, or until the payout has been dispatched. An empty deposit list before then is expected.
2. Use `GET /Settlements/{id}/deposits` to retrieve deposits for the settlement.
3. Review deposit status, amount, `feeAmount`, method, and `dispatchedDate` on every record returned.
4. Confirm the settlement has a deposit with status `paid`.
5. 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 for the period-level fee rollup, adjustments, and net fundable amount.
* Use Residual Reporting for guidance on how deposit fees factor into monthly residual calculations.
* Use Batches to review grouped payment activity behind a settlement.
* Use Payment Settlement Detail for payment-level settlement context and to trace payments through the settlement lifecycle.
* Use [Reporting](https://developers.bead.xyz/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.

> **Use the collection endpoints for this, not the path-scoped ones.** `MerchantIds` only functions on the collection endpoints `GET /Settlements` and `GET /Batches`. On `GET /Merchants/{merchantId}/settlements` it is accepted and returns HTTP 200 but has no effect: the path parameter is the only merchant scope, and passing a different merchant's ID returns the same path-scoped records. Confirmed by direct testing. Do not attempt a multi-merchant pull through a merchant-scoped path.

> **Date format on Settlement endpoints.** `From` and `To` on `GET /Settlements` and `GET /Batches` are date values (`2026-07-15`). A full ISO timestamp with a time component fails validation, confirmed by direct testing on both endpoints. This differs from the Reporting payment endpoints, where `From` and `To` are date-time values and accept either form.

> **`To` appears to be exclusive.** A same-day window (`From` and `To` set to the same date) returns zero records, while extending `To` by one day returns the expected record. Extend `To` past the last day you want included, and account for this when you advance a sync window so you do not skip a day.

**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 both are orderable (`SortBy=created`, `SortBy=updated`). `updated` is the field to compare against your last stored value when deciding whether a settlement changed.
* **`From` and `To` filter on the settlement's `created` date, not `updated`.** They return settlements *created* in the window. `updated` is sortable but is not range filterable: there is no `updatedFrom` / `updatedTo` or `ModifiedSince` parameter on `GET /Settlements`.

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` and `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.

Compare both `updated` and `status` in step 2 rather than relying on `updated` alone. Treating a changed `status` as authoritative means your job still behaves correctly even if a given transition doesn't move the timestamp.

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 against 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 status fields that are not the same thing**

Payment history responses do **not** carry a payment level settlement processing status. The two status fields you will actually encounter live on different entities and describe different state machines:

| Field         | Lives on                                                            | Values                                  | What it actually means                                                                                                    |
| ------------- | ------------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `batchStatus` | Payment (Reporting APIs), and the batch record's own `status` field | `open`, `closed`, `hold`                | Where the **batch** containing this payment is in the batching process. Null on the payment until the payment is batched. |
| `status`      | Settlement record                                                   | `cleared`, `hold`, `dispatched`, `paid` | Where the **settlement** itself is in the funding and payout lifecycle.                                                   |

Both enums contain a value named `hold`, and they do not mean the same thing. A `batchStatus` of `hold` is a batch pending review. A settlement `status` of `hold` is the settlement itself held before funding, and in that case the settlement record's `holdReason` explains why (it is null unless `status` is `hold`).

These are sequential, not parallel: a payment is batched first, and the batch is later tied to a settlement that funds. So a payment can have `batchStatus` of `closed` while its settlement is still `cleared` rather than `paid`. That is not a contradiction, it just means the batch finished closing before the settlement finished funding. Don't infer funding or payout state from `batchStatus`, and don't infer batch state from settlement `status`.

> **There is no `paymentSettlementStatusCode` field.** Earlier revisions of this page described a `paymentSettlementStatusCode` field on the Reporting payment objects, with its own status enum. That field does not exist in the payment response schema on [Merchant](/reporting/merchant-payments), [Partner](/reporting/partner-payments), or [Terminal](/reporting/terminal-payments) Payments, and it is not available as a filter. Use the settlement fields described below instead.

**Settlement fields on a payment, by scope**

The settlement fields available on a payment differ slightly depending on which Reporting endpoint returned it:

| Endpoint                                                                         | `settlementId` | `batchId` | `batchStatus` | `batchPeriod` |
| -------------------------------------------------------------------------------- | -------------- | --------- | ------------- | ------------- |
| [Merchant Payments](/reporting/merchant-payments) `GET /Merchants/{id}/payments` | Yes            | Yes       | Yes           | Yes           |
| [Partner Payments](/reporting/partner-payments) `GET /Partners/{id}/payments`    | Yes            | Yes       | Yes           | Yes           |
| [Terminal Payments](/reporting/terminal-payments) `GET /Terminals/{id}/payments` | Yes            | Yes       | Yes           | No            |

All of these are null until the payment is batched. If your reconciliation job needs `batchPeriod`, read the payment from Merchant or Partner scope rather than Terminal scope. See [Payment Settlement Detail](/settlement/payment-settlement-detail) for the state progression these fields move through.

**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, alongside `grossFundableAmount`, `totalTransactionFees`, `depositFees`, `totalFees`, `adjustments`, and `netAdjustmentTotal`. 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:

* Read `batchIds` off the settlement record, or call [GET /Settlements/{id}/batches](/settlement/batches) to get the batch records themselves.
* Read `paymentIds` off each batch.
* Cross reference those IDs against your own payment records, or look them up individually.

Reading `batchIds` from the settlement you already have avoids an extra call when all you need are the batch identifiers. Call `GET /Settlements/{id}/batches` when you need the batch records' own fields, such as `status`, `batchPeriod`, `transactionCount`, `totalFees`, or `paymentIds`.

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=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. `PageSize` defaults to `50` and its maximum is `100` on all of them.

If you previously read guidance here suggesting the Reporting family defaults to `Page` equals `1`, that was inaccurate and has been corrected.

**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 a settlement is in `hold`, read `holdReason` to understand why before escalating.
5. If you need batch level detail for a changed settlement, use its `batchIds` or call [GET /Settlements/{id}/batches](/settlement/batches).
6. If you need transaction level detail, use `paymentIds` from each batch to cross reference your own records.
7. Use `netFundableAmount` from the settlement record as your reconciled total for that merchant or period.
8. 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 APIs.

> **Building residual or revenue-share reports?** Start with Settlements for fee and net fundable amount data, then use Residual Reporting for the full integration pattern. Use Reporting APIs to pull the underlying payment activity for the period.

> **Reporting on a Split Funding Program partner distribution?** The payment-history endpoints below cover standard payment activity. Use Split Funding Reports instead for split funding transaction detail and the partner distribution summary.

**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 webhook notifications, it does not replace them.

**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           |

Note: terminal scope returns a reduced and different field set from partner and merchant scope. Read the response schema note below before reusing a parser across scopes.

All three endpoints accept the same query parameters. The response schema is **not** the same at every scope:

* `/Merchants/{id}/payments` and `/Partners/{id}/payments` return `PagedDataResultOfPaymentResponse`, whose `data` array holds `PaymentResponse` records.
* `/Terminals/{id}/payments` returns `PagedDataResultOfPaymentInfo`, whose `data` array holds `PaymentInfo` records.

Both envelopes expose `data`, `total`, and `page`, so pagination logic can be shared across scopes. The record shape inside `data` cannot.

Fields present on `PaymentResponse` but **absent** from `PaymentInfo`: `batchPeriod`, `locationId`, `locationName`, `merchantId`, `tenderType`, `transferType`, `originalSenderAddress`, and the four refund lifecycle fields `refundLifecycleStatus`, `refundInitiatedAt`, `refundCompletedAt`, `refundFailureReason`.

Fields present on `PaymentInfo` but **absent** from `PaymentResponse`: `trackingId`, `pageId`, `reqCurrencyId`, `payCurrencyId`, `emailReceipt`, `smsReceipt`, `cartItems`, `externalId`, `merchantLocation`, `transactions`, `paymentNotifications`, `webhookUrls`, `refundEmail`, `expiration`, `quoteExpiration`, `paymentAddress`, `redirectUrl`.

> **Do not assume one payment model across scopes.** If your logic keys on `merchantId`, `tenderType`, `transferType`, or the refund lifecycle fields, read from merchant or partner scope. Terminal scope does not return those values. Two further differences to code around: `customerId` is an object on `PaymentResponse` and a string on `PaymentInfo`, and `created` is always present on `PaymentResponse` but nullable on `PaymentInfo`.

See Pagination and Sorting for the full parameter reference.

**Split funding amounts are not on the payment record**

None of the three payment-history endpoints return the split funding amounts or Bead's fee on the payment record. Use Split Funding Reports for that data.

**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 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 for query parameter definitions.
* Use Partner Payments for portfolio-level history.
* Use Merchant Payments for store-level history.
* Use Terminal Payments for device-level history.
* Use Split Funding Reports for Split Funding Program partner distribution reconciliation.
* Use Settlements when you need fee totals, adjustments, and net fundable amount.
* Use 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.

#### Response schema differs at terminal scope

The three scopes accept the same query parameters, but they do not all return the same payload.

* `/Merchants/{id}/payments` and `/Partners/{id}/payments` return `PagedDataResultOfPaymentResponse`, holding `PaymentResponse` records.
* `/Terminals/{id}/payments` returns `PagedDataResultOfPaymentInfo`, holding `PaymentInfo` records.

Each envelope exposes `data`, `total`, and `page`, so the paging loop is the same at every scope. The record shape inside `data` is not.

`PaymentInfo` does not carry `batchPeriod`, `locationId`, `locationName`, `merchantId`, `tenderType`, `transferType`, `originalSenderAddress`, or the four refund lifecycle fields (`refundLifecycleStatus`, `refundInitiatedAt`, `refundCompletedAt`, `refundFailureReason`). It does carry fields that `PaymentResponse` omits, including `trackingId`, `pageId`, `reqCurrencyId`, `payCurrencyId`, `emailReceipt`, `smsReceipt`, `cartItems`, `externalId`, `merchantLocation`, `transactions`, `paymentNotifications`, `webhookUrls`, `refundEmail`, `expiration`, `quoteExpiration`, `paymentAddress`, and `redirectUrl`.

> **Filter support is not the same as field presence.** `TenderType` is accepted as a filter on all three endpoints, but the `tenderType` value is not present on the records returned by terminal scope. If your report needs to read or group by tender type, query merchant or partner scope. Also note that `customerId` is an object on `PaymentResponse` and a string on `PaymentInfo`, and `created` is nullable on `PaymentInfo`.

#### Pagination, sorting, and filters

* Always supply `Page` and `PageSize`. `Page` is zero based and defaults to `0`. `PageSize` defaults to `50` and has a maximum of `100`. The default is fine for UI scroll views. For automated exports, stay within the maximum: use `PageSize=100` and iterate pages rather than asking for a larger page.
* Sorting on the created timestamp in descending order is the fastest way to pull "latest payments". `SortBy` accepts only `statusCode`, `expiration`, `quoteExpiration`, `created`, and `updated`. `SortDirection` accepts `asc` or `desc`.
* Narrow `From` and `To` when possible. Both are date-time values. Smaller windows reduce query cost and speed up responses.
* Status filtering lets you retrieve only exceptions such as `underpaid`, `overpaid`, or `invalid`. The parameter is `StatusCode`, and it is an array: repeat it to pass more than one value, for example `StatusCode=underpaid&StatusCode=overpaid`.

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 `From` and `To` 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 `From` to the timestamp of the last item. |
| Exception monitoring      | Use `StatusCode=underpaid&StatusCode=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 a `PageSize` above the maximum of 100 | 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.
* Do not reuse one response parser across all three scopes. Terminal scope returns `PaymentInfo`, which is a different field set.
* 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

`/Partners/{id}/payments`, `/Merchants/{id}/payments`, and `/Terminals/{id}/payments` all accept the same query parameters: the same pagination, sorting, and filter contract applies to all three.

They do **not** all return the same response schema. See the Response schema by scope section below before you write a shared parser.

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`              | date-time        | Yes      | None      | Start of the payment creation-time window, in UTC. Accepts either a full ISO 8601 timestamp (`2025-09-01T00:00:00Z`) or a date-only value (`2025-09-01`). |
| `To`                | date-time        | Yes      | None      | End of the payment creation-time window, in UTC. Accepts either a full ISO 8601 timestamp or a date-only value, same as `From`.                           |
| `Page`              | integer          | Yes      | `0`       | Zero-based page number. The first page is `0`, not `1`.                                                                                                   |
| `PageSize`          | integer          | Yes      | `50`      | Records per page. Maximum `100`.                                                                                                                          |
| `SortBy`            | string           | No       | `created` | Field to sort on. Allowed values: `statusCode`, `expiration`, `quoteExpiration`, `created`, `updated`.                                                    |
| `SortDirection`     | string           | No       | `desc`    | `asc` or `desc`.                                                                                                                                          |
| `StatusCode`        | array of strings | No       | None      | Filter by payment status. Repeat the parameter for multiple values. See [Payment Statuses](/payments/payment-statuses).                                   |
| `TenderType`        | array of strings | No       | None      | Filter by tender type. Repeat the parameter for multiple values. See [Tender Types](/reference-guide/enumerations-and-schemas/tender-types).              |
| `TrackingId`        | string           | No       | None      | Filter by tracking identifier.                                                                                                                            |
| `PageId`            | string           | No       | None      | Opaque cursor for continuation, echoed from a prior response.                                                                                             |
| `Reference`         | string           | No       | None      | Filter by merchant reference, e.g. invoice number. Exact, case-sensitive match.                                                                           |
| `PaymentCode`       | string           | No       | None      | Filter by payment code. Case-insensitive substring match.                                                                                                 |
| `CustomerId`        | string           | No       | None      | Filter by customer ID. Exact match.                                                                                                                       |
| `DescriptionSearch` | string           | No       | None      | Case-sensitive text search over the description field.                                                                                                    |
| `ReqCurrencyId`     | integer          | No       | None      | Filter by requested currency ID.                                                                                                                          |
| `PayCurrencyId`     | integer          | No       | None      | Filter by payment currency ID.                                                                                                                            |
| `LocationName`      | string           | No       | None      | Filter by location name. Case-insensitive substring match. Partner and Merchant scope only, see below.                                                    |
| `TerminalName`      | string           | No       | None      | Filter by terminal name. Case-insensitive substring match. Partner and Merchant scope only, see below.                                                    |

> **Parameter casing:** all query parameters on these endpoints are capitalized (`From`, `To`, `Page`, `PageSize`, `StatusCode`, and so on). Lowercase parameter names have not been verified as accepted, use the capitalized form shown above.

> **Every parameter above is accepted on all three endpoints, but two of them only filter on two of the three.** `LocationName` and `TerminalName` filter as expected on [Partner Payments](/reporting/partner-payments) and [Merchant Payments](/reporting/merchant-payments). On [Terminal Payments](/reporting/terminal-payments) both are accepted, return HTTP 200, and have no effect on the result set: a value matching nothing returns the same records as an unfiltered request. Confirmed by direct testing. Do not rely on either parameter at terminal scope.

> **`To` appears to be exclusive at the boundary.** On the Settlement endpoints, a same-day window (`From` and `To` set to the same date) returns zero records, while extending `To` by one day returns the expected record. Treat `To` as exclusive and extend it past the last day you want included. This has not been characterized on the payment endpoints.

> **Note:** `merchantId` and `locationId` as exact-match filters are not available. Neither is `settlementId` or `batchId`. Use `LocationName` and `TerminalName` for name-based filtering, or filter results client-side. To go from a settlement to its payments, see [Settlement Reconciliation Patterns](/settlement/settlement-reconciliation-patterns).

> **Some filters have no matching response field at merchant and partner scope.** `TrackingId`, `PageId`, `ReqCurrencyId`, and `PayCurrencyId` are accepted as filters everywhere, but their values are only returned on records from `/Terminals/{id}/payments`. Filter on them if you already hold the value, do not expect to read them back at merchant or partner scope.

**Payment status values**

Valid values for `StatusCode`:

| Value               | Description                                                                                                                                 |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `created`           | Payment has been created.                                                                                                                   |
| `processing`        | Payment is being processed.                                                                                                                 |
| `completed`         | Paid. The customer sent the requested amount. Final status.                                                                                 |
| `overpaid`          | Paid. The customer sent more than the requested amount. The requested amount settles and the excess is returned to the payer. Final status. |
| `underpaid`         | Not paid. The customer sent less than the requested amount. Final status.                                                                   |
| `expired`           | Payment expired before completion. Final status.                                                                                            |
| `abandoned`         | Payment was created but never started, and was closed by Bead. Final status.                                                                |
| `invalid`           | Payment is invalid. Final status.                                                                                                           |
| `cancelled`         | Payment was cancelled. Final status.                                                                                                        |
| `partiallyRefunded` | Reserved. Accepted as a filter value. See [Payment Statuses](/payments/payment-statuses).                                                   |
| `fullyRefunded`     | Reserved. Accepted as a filter value. See [Payment Statuses](/payments/payment-statuses).                                                   |

A reclaim does not change `statusCode`. Read `refundLifecycleStatus` on the returned records to see reclaim progress. See [Payment Statuses](/payments/payment-statuses) for full status handling guidance.

**Response schema by scope**

The parameter contract is shared. The response object is not.

| Scope                                                                        | Response object   | Notable difference                                                                                                                                                                                                                                                                                            |
| ---------------------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Partner Payments](/reporting/partner-payments) `/Partners/{id}/payments`    | `PaymentResponse` | Full merchant and location context, tender and transfer type, refund lifecycle fields                                                                                                                                                                                                                         |
| [Merchant Payments](/reporting/merchant-payments) `/Merchants/{id}/payments` | `PaymentResponse` | Same as Partner scope                                                                                                                                                                                                                                                                                         |
| [Terminal Payments](/reporting/terminal-payments) `/Terminals/{id}/payments` | `PaymentInfo`     | Device oriented fields instead: `trackingId`, `pageId`, `reqCurrencyId`, `payCurrencyId`, `emailReceipt`, `smsReceipt`, `cartItems`, `externalId`, `merchantLocation`, `transactions`, `paymentNotifications`, `webhookUrls`, `refundEmail`, `expiration`, `quoteExpiration`, `paymentAddress`, `redirectUrl` |

Terminal scope does **not** return `batchPeriod`, `locationId`, `locationName`, `merchantId`, `tenderType`, `transferType`, `originalSenderAddress`, or any of the four refund lifecycle fields. Two further differences to watch:

* `customerId` is an object at merchant and partner scope, and a string at terminal scope.
* `created` and `updated` are always present at merchant and partner scope. At terminal scope both are declared nullable, so handle null defensively even though they were populated in testing.

Do not write one response parser and reuse it across all three scopes.

**Settlement status on payment history**

Payment history responses do not include a `paymentSettlementStatusCode` field, and it is not available as a filter on these endpoints. For settlement lifecycle status, each payment carries settlement linkage fields:

| Field          | Partner | Merchant | Terminal |
| -------------- | ------- | -------- | -------- |
| `settlementId` | Yes     | Yes      | Yes      |
| `batchId`      | Yes     | Yes      | Yes      |
| `batchStatus`  | Yes     | Yes      | Yes      |
| `batchPeriod`  | Yes     | Yes      | No       |

All of these are null until the payment is batched. `completed` and `overpaid` payments are batched. `batchStatus` carries `open`, `closed`, or `hold`, which is a batch state, not a settlement state. Settlement state itself lives on the settlement record.

See [Payment Settlement Detail](/settlement/payment-settlement-detail) for the full pattern, [Settlement Reconciliation Patterns](/settlement/settlement-reconciliation-patterns) for portfolio scale reconciliation, and [Settlement Status Codes](/reference-guide/enumerations-and-schemas/settlement-status-codes) for the status enums that apply to settlement-side objects.

**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 (zero-based).                 |

The envelope is the same on all three endpoints, only the objects inside `data` differ. `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 "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 "SortBy=created" \
  --data-urlencode "SortDirection=desc" \
  --data-urlencode "StatusCode=completed"
```

**Best practices**

* Always supply `From`, `To`, `Page`, and `PageSize` on every request.
* Use `SortBy=created` with `SortDirection=desc` to retrieve the most recent payments first.
* Narrow `From` and `To` as much as possible, smaller windows reduce query cost and improve response time.
* Keep `PageSize` at or below the maximum of `100`. A larger value is rejected.
* 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.
* For paid payments, filter with `StatusCode=completed&StatusCode=overpaid`.
* Use `StatusCode=overpaid&StatusCode=underpaid` combined with a narrow date window, then read `refundLifecycleStatus`, to monitor reclaims.
* Treat `total` as a snapshot, new payments arriving after your query will change it on the next call.
* Use `LocationName` or `TerminalName` to narrow a query to a specific store or device before falling back to client-side filtering.
* Use the `updated` timestamp to detect payment changes, but not to detect settlement events. Assigning a settlement to a payment does not change the payment record.

**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. Each payment carries the settlement linkage fields `settlementId`, `batchId`, `batchPeriod`, and `batchStatus`. For period level fees, funded amounts, and payout state, use the [Settlement APIs](/settlement).

This endpoint returns the same response schema as [Merchant Payments](/reporting/merchant-payments). [Terminal Payments](/reporting/terminal-payments) returns a different schema.

**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, date-time | Yes      | Start of the payment creation-time window, in UTC. Accepts either a full ISO 8601 timestamp (`2025-09-01T00:00:00Z`) or a date-only value (`2025-09-01`).                                                    |
| `To`                | string, date-time | Yes      | End of the payment creation-time window, in UTC. Accepts either a full ISO 8601 timestamp or a date-only value, same as `From`.                                                                              |
| `Page`              | integer           | Yes      | Zero based page number. The first page is `0`, not `1`. Default `0`.                                                                                                                                         |
| `PageSize`          | integer           | Yes      | Items per page. Default `50`, maximum `100`.                                                                                                                                                                 |
| `StatusCode`        | array of strings  | No       | One or more of `created`, `processing`, `completed`, `overpaid`, `underpaid`, `expired`, `abandoned`, `invalid`, `cancelled`. The reserved values `partiallyRefunded` and `fullyRefunded` are also accepted. |
| `TenderType`        | array of strings  | No       | Filter by one or more tender types. See [Tender Types](/reference-guide/enumerations-and-schemas/tender-types).                                                                                              |
| `TrackingId`        | string            | No       | Filter by a specific tracking identifier                                                                                                                                                                     |
| `PageId`            | string            | No       | Opaque cursor for continuation, echoed from a prior response                                                                                                                                                 |
| `ReqCurrencyId`     | integer           | No       | Requested currency id filter                                                                                                                                                                                 |
| `PayCurrencyId`     | integer           | No       | Payment currency id filter                                                                                                                                                                                   |
| `CustomerId`        | string            | No       | Filter by customer id. Exact match.                                                                                                                                                                          |
| `DescriptionSearch` | string            | No       | Case sensitive text search over the description                                                                                                                                                              |
| `PaymentCode`       | string            | No       | Filter by payment code. Case-insensitive substring match.                                                                                                                                                    |
| `Reference`         | string            | No       | Filter by merchant reference such as invoice number. Exact, case-sensitive match.                                                                                                                            |
| `LocationName`      | string            | No       | Filter by location name. Case-insensitive substring match.                                                                                                                                                   |
| `TerminalName`      | string            | No       | Filter by terminal name. Case-insensitive substring match.                                                                                                                                                   |
| `SortBy`            | string            | No       | Field to sort by. Allowed values: `statusCode`, `expiration`, `quoteExpiration`, `created`, `updated`.                                                                                                       |
| `SortDirection`     | string            | No       | `asc` or `desc`                                                                                                                                                                                              |

> **Parameter casing:** all query parameters on this endpoint are capitalized (`From`, `To`, `Page`, `PageSize`, and so on).

> **No exact-match `merchantId` or `locationId` filter.** This endpoint does not support filtering to an exact merchant or location ID. Use `LocationName` for name-based location filtering, or filter the `merchantId` field client-side after retrieving results.

> **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`.

> **Some filters have no matching response field.** `TrackingId`, `PageId`, `ReqCurrencyId`, and `PayCurrencyId` are accepted as filters here, but their values are not returned on this endpoint's payment objects. Filter on them if you already hold the value, do not expect to read them back.

For more about pagination and sorting, see [Pagination and Sorting](/reporting/payment-history-concepts/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 "StatusCode=completed"
```

**Successful response 200**

```json
{
  "data": [
    {
      "id": "pay_01J8Z1R9K6",
      "terminalId": "term_12345678",
      "terminalName": "Front Counter 1",
      "locationId": "loc_bfdc6a7f",
      "locationName": "Downtown Flagship",
      "merchantId": "mer_4e5a13aa",
      "paymentCode": "PMT-10452",
      "settlementId": null,
      "batchId": null,
      "batchPeriod": null,
      "batchStatus": "open",
      "tenderType": "usdcBase",
      "transferType": "sale",
      "created": "2025-09-01T14:22:11.015Z",
      "updated": "2025-09-01T14:22:12.102Z",
      "statusCode": "completed",
      "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",
      "originalSenderAddress": null,
      "description": "Sunglasses",
      "customerId": null,
      "conversions": [],
      "refundLifecycleStatus": "notStarted",
      "refundInitiatedAt": null,
      "refundCompletedAt": null,
      "refundFailureReason": null,
      "fundingStatus": null,
      "fundingPaidAt": null
    }
  ],
  "total": 1,
  "page": 0
}
```

**Field reference**

| Field                        | Type                          | Description                                                                                                                                                                                                                                                                                                                                         |
| ---------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                         | string                        | Unique payment identifier                                                                                                                                                                                                                                                                                                                           |
| `terminalId`, `terminalName` | string                        | Terminal that originated the payment                                                                                                                                                                                                                                                                                                                |
| `locationId`, `locationName` | string, `locationId` nullable | Location that originated the payment                                                                                                                                                                                                                                                                                                                |
| `merchantId`                 | string                        | Merchant that originated the payment                                                                                                                                                                                                                                                                                                                |
| `paymentCode`                | string                        | Short code for user facing references                                                                                                                                                                                                                                                                                                               |
| `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. `completed` and `overpaid` payments are batched.                                                                                                                                                                                                                                  |
| `batchPeriod`                | date or null                  | Date associated with the batch period.                                                                                                                                                                                                                                                                                                              |
| `batchStatus`                | string or null                | Status of the batch containing this payment: `open`, `closed`, or `hold`. This is a batch state, not a settlement state.                                                                                                                                                                                                                            |
| `tenderType`                 | string                        | Tender type used for the payment                                                                                                                                                                                                                                                                                                                    |
| `transferType`               | string                        | `sale`, `refund`, or `chargeback`                                                                                                                                                                                                                                                                                                                   |
| `created`                    | date-time                     | ISO 8601 timestamp in UTC. Always present.                                                                                                                                                                                                                                                                                                          |
| `updated`                    | date-time                     | ISO 8601 timestamp in UTC. Sortable.                                                                                                                                                                                                                                                                                                                |
| `statusCode`                 | string                        | Current payment status. Every status other than `created` and `processing` is final. See [Payment Statuses](/payments/payment-statuses).                                                                                                                                                                                                            |
| `amounts`                    | object                        | `requested`, `paid`, `settleableAmount`, and `partnerFees`                                                                                                                                                                                                                                                                                          |
| `reference`, `description`   | string or null                | Merchant supplied metadata fields                                                                                                                                                                                                                                                                                                                   |
| `originalSenderAddress`      | string or null                | Originating sender address, where applicable to the tender                                                                                                                                                                                                                                                                                          |
| `customerId`                 | object or null                | Customer record associated with the payment. This is an object, not a bare id string.                                                                                                                                                                                                                                                               |
| `conversions`                | array                         | Currency conversion details for crypto and wallet tenders, when applicable                                                                                                                                                                                                                                                                          |
| `refundLifecycleStatus`      | string or null                | Progress of the reclaim return to the payer. One of `notStarted`, `emailSent`, `customerClaimed`, `processing`, `completed`, `failed`, `rejected`. A value of `null` means the same as `notStarted`. See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status).                                                     |
| `refundInitiatedAt`          | date-time or null             | The first time Bead emailed the payer reclaim instructions or the payer started a claim, whichever came first. Null until then.                                                                                                                                                                                                                     |
| `refundCompletedAt`          | date-time or null             | When Bead confirmed the return to the payer. Null until then.                                                                                                                                                                                                                                                                                       |
| `refundFailureReason`        | string or null                | Populated when a return attempt failed                                                                                                                                                                                                                                                                                                              |
| `fundingStatus`              | string or null                | Where the funds for this payment stand on their way from the payment provider to Bead. One of `pending`, `dispatched`, `paid`, `error`. Null when no provider funding is expected for the payment. This describes funding into Bead, not the merchant deposit. For the merchant deposit, use `settlementId` and the [Settlement APIs](/settlement). |
| `fundingPaidAt`              | date-time or null             | When the provider funding for this payment reached `paid`. Null until then.                                                                                                                                                                                                                                                                         |

The response envelope is `data`, `total`, and `page`. `PageSize` is not echoed in the response.

> **Fields this endpoint does not return.** `merchantLocation`, `emailReceipt`, `smsReceipt`, `trackingId`, `pageId`, `reqCurrencyId`, `payCurrencyId`, `cartItems`, `externalId`, `expiration`, `quoteExpiration`, `paymentAddress`, `redirectUrl`, `transactions`, `paymentNotifications`, `webhookUrls`, and `refundEmail` are not part of this endpoint's response. Several of them are returned by [Terminal Payments](/reporting/terminal-payments), which uses a different schema.

> **There is no `paymentSettlementStatusCode` field.** It is not returned here and is not accepted as a filter. Use `settlementId`, `batchId`, `batchPeriod`, and `batchStatus` together with [Settlements](/settlement/settlements), [Batches](/settlement/batches), and [Deposits](/settlement/deposits).

**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, up to the maximum of 100        | Balances throughput and latency                                                                                                                                                                                                                                                                                                                             |
| Filter by `LocationName` or `TerminalName` when the partner has many stores or devices                | Speeds up dashboards and reduces response sizes                                                                                                                                                                                                                                                                                                             |
| Use the `updated` timestamp to detect payment changes                                                 | Helps you refresh only modified rows                                                                                                                                                                                                                                                                                                                        |
| Do not use `updated` to detect settlement events                                                      | Assigning a settlement to a payment does not change the payment record. See [Settlement Reconciliation Patterns](/settlement/settlement-reconciliation-patterns).                                                                                                                                                                                           |
| Rely on webhooks for real time state changes                                                          | Payment webhooks notify you of status transitions and reclaim progress without polling, and the `reclaimStatusChanged` [partner webhook](/onboarding/partner-webhooks) covers reclaim progress across all of your merchants. To catch up on reclaim returns, filter with `StatusCode=overpaid` and `StatusCode=underpaid` and read `refundLifecycleStatus`. |
| Pull the whole portfolio from the Settlement side with `MerchantIds` rather than looping per merchant | One call covers every merchant under the partner. See [Settlement Reconciliation Patterns](/settlement/settlement-reconciliation-patterns).                                                                                                                                                                                                                 |
| Do not treat `fundingStatus` as the merchant deposit                                                  | It tracks provider funding into Bead. The merchant deposit is tracked on the settlement                                                                                                                                                                                                                                                                     |

**Related endpoints**

| Action                   | Endpoint                                                                                  |
| ------------------------ | ----------------------------------------------------------------------------------------- |
| Merchant payment history | `GET /Merchants/{id}/payments`                                                            |
| Terminal payment history | `GET /Terminals/{id}/payments`                                                            |
| Webhook event reference  | See [Payment Webhooks](/payments/payment-webhooks)                                        |
| Reclaim status           | See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status) |

Use Partner Payments for portfolio level analytics and monitoring, then drill down with [Merchant Payments](/reporting/merchant-payments) and [Terminal Payments](/reporting/terminal-payments) for store and device level investigations, and use [Settlement](/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. Each payment carries the settlement linkage fields `settlementId`, `batchId`, `batchPeriod`, and `batchStatus`, so you can tell which batch and settlement a payment belongs to without a second call. For period level fees, funded amounts, and payout state, use the [Settlement APIs](/settlement/settlements).

**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, date-time | Yes      | Start of the payment creation-time window, in UTC. Accepts either a full ISO 8601 timestamp (`2025-09-01T00:00:00Z`) or a date-only value (`2025-09-01`).                                                    |
| `To`                | string, date-time | Yes      | End of the payment creation-time window, in UTC. Accepts either a full ISO 8601 timestamp or a date-only value, same as `From`.                                                                              |
| `Page`              | integer           | Yes      | Zero based page number. The first page is `0`, not `1`. Default `0`.                                                                                                                                         |
| `PageSize`          | integer           | Yes      | Items per page. Default `50`, maximum `100`.                                                                                                                                                                 |
| `StatusCode`        | array of strings  | No       | One or more of `created`, `processing`, `completed`, `overpaid`, `underpaid`, `expired`, `abandoned`, `invalid`, `cancelled`. The reserved values `partiallyRefunded` and `fullyRefunded` are also accepted. |
| `TenderType`        | array of strings  | No       | Filter by one or more tender types. See [Tender Types](/reference-guide/enumerations-and-schemas/tender-types).                                                                                              |
| `TrackingId`        | string            | No       | Filter by a specific tracking identifier                                                                                                                                                                     |
| `PageId`            | string            | No       | Opaque cursor for continuation, echoed from a prior response                                                                                                                                                 |
| `ReqCurrencyId`     | integer           | No       | Requested currency id filter                                                                                                                                                                                 |
| `PayCurrencyId`     | integer           | No       | Payment currency id filter                                                                                                                                                                                   |
| `CustomerId`        | string            | No       | Filter by customer id. Exact match.                                                                                                                                                                          |
| `DescriptionSearch` | string            | No       | Case sensitive text search over the description                                                                                                                                                              |
| `PaymentCode`       | string            | No       | Filter by payment code. Case-insensitive substring match.                                                                                                                                                    |
| `Reference`         | string            | No       | Filter by merchant reference such as invoice number. Exact, case-sensitive match.                                                                                                                            |
| `LocationName`      | string            | No       | Filter by location name. Case-insensitive substring match.                                                                                                                                                   |
| `TerminalName`      | string            | No       | Filter by terminal name. Case-insensitive substring match.                                                                                                                                                   |
| `SortBy`            | string            | No       | Field to sort by. Allowed values: `statusCode`, `expiration`, `quoteExpiration`, `created`, `updated`.                                                                                                       |
| `SortDirection`     | string            | No       | `asc` or `desc`                                                                                                                                                                                              |

> **Parameter casing:** all query parameters on this endpoint are capitalized (`From`, `To`, `Page`, `PageSize`, and so on), matching the pattern used on the Settlement endpoints. Lowercase parameter names have not been verified as accepted, use the capitalized form shown above.

> **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`.

> **Some filters have no matching response field.** `TrackingId`, `PageId`, `ReqCurrencyId`, and `PayCurrencyId` are accepted as filters on this endpoint, but their values are not returned on the payment objects this endpoint produces. Filter on them if you already hold the value; do not expect to read them back. They are present on the records returned by [Terminal Payments](/reporting/terminal-payments), which uses a different response schema.

> **There is no `paymentSettlementStatusCode` field.** It is not returned on this endpoint's payment objects and is not accepted as a filter. Settlement state is carried by `settlementId`, `batchId`, `batchPeriod`, and `batchStatus`, described below. There is also no `settlementId` or `batchId` filter, see [Settlement Reconciliation Patterns](/settlement/settlement-reconciliation-patterns) for how to go from a settlement to its payments.

**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",
      "terminalId": "term_12345678",
      "terminalName": "Front Counter 1",
      "locationId": "loc_bfdc6a7f",
      "locationName": "Downtown Flagship",
      "merchantId": "mer_4e5a13aa",
      "paymentCode": "PMT-10452",
      "settlementId": null,
      "batchId": null,
      "batchPeriod": null,
      "batchStatus": "open",
      "tenderType": "usdcBase",
      "transferType": "sale",
      "created": "2025-09-01T14:20:03.881Z",
      "updated": "2025-09-01T14:22:12.102Z",
      "statusCode": "completed",
      "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",
      "originalSenderAddress": null,
      "description": "Sunglasses",
      "customerId": null,
      "conversions": [],
      "refundLifecycleStatus": "notStarted",
      "refundInitiatedAt": null,
      "refundCompletedAt": null,
      "refundFailureReason": null,
      "fundingStatus": null,
      "fundingPaidAt": null
    }
  ],
  "total": 1,
  "page": 0
}
```

**Field reference**

| Field                        | Type                          | Description                                                                                                                                                                                                                                                                                                                                                     |
| ---------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                         | string                        | Unique payment identifier                                                                                                                                                                                                                                                                                                                                       |
| `terminalId`, `terminalName` | string                        | Terminal that originated the payment                                                                                                                                                                                                                                                                                                                            |
| `locationId`, `locationName` | string, `locationId` nullable | Location that originated the payment                                                                                                                                                                                                                                                                                                                            |
| `merchantId`                 | string                        | Merchant that originated the payment                                                                                                                                                                                                                                                                                                                            |
| `paymentCode`                | string                        | Short code for user facing references                                                                                                                                                                                                                                                                                                                           |
| `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. `completed` and `overpaid` payments are batched.                                                                                                                                                                                                                                              |
| `batchPeriod`                | date or null                  | Date associated with the batch period.                                                                                                                                                                                                                                                                                                                          |
| `batchStatus`                | string or null                | Status of the batch containing this payment: `open`, `closed`, or `hold`. Null until the payment is batched. This is a batch state, not a settlement state.                                                                                                                                                                                                     |
| `tenderType`                 | string                        | Tender type used for the payment. See [Tender Types](/reference-guide/enumerations-and-schemas/tender-types).                                                                                                                                                                                                                                                   |
| `transferType`               | string                        | `sale`, `refund`, or `chargeback`                                                                                                                                                                                                                                                                                                                               |
| `created`                    | date-time                     | ISO 8601 timestamp in UTC. Always present.                                                                                                                                                                                                                                                                                                                      |
| `updated`                    | date-time                     | ISO 8601 timestamp in UTC. Sortable.                                                                                                                                                                                                                                                                                                                            |
| `statusCode`                 | string                        | Current payment status. Every status other than `created` and `processing` is final. See [Payment Statuses](/payments/payment-statuses).                                                                                                                                                                                                                        |
| `amounts`                    | object                        | `requested`, `paid`, `settleableAmount`, and `partnerFees`                                                                                                                                                                                                                                                                                                      |
| `reference`, `description`   | string or null                | Merchant supplied metadata fields                                                                                                                                                                                                                                                                                                                               |
| `originalSenderAddress`      | string or null                | Originating sender address, where applicable to the tender                                                                                                                                                                                                                                                                                                      |
| `customerId`                 | object or null                | Customer record associated with the payment. This is an object, not a bare id string.                                                                                                                                                                                                                                                                           |
| `conversions`                | array                         | Currency conversion details for crypto and wallet tenders, when applicable                                                                                                                                                                                                                                                                                      |
| `refundLifecycleStatus`      | string or null                | Progress of the reclaim return to the payer. One of `notStarted`, `emailSent`, `customerClaimed`, `processing`, `completed`, `failed`, `rejected`. A value of `null` means the same as `notStarted`. See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status).                                                                 |
| `refundInitiatedAt`          | date-time or null             | The first time Bead emailed the payer reclaim instructions or the payer started a claim, whichever came first. Null until then.                                                                                                                                                                                                                                 |
| `refundCompletedAt`          | date-time or null             | When Bead confirmed the return to the payer. Null until then.                                                                                                                                                                                                                                                                                                   |
| `refundFailureReason`        | string or null                | Populated when a return attempt failed                                                                                                                                                                                                                                                                                                                          |
| `fundingStatus`              | string or null                | Where the funds for this payment stand on their way from the payment provider to Bead. One of `pending`, `dispatched`, `paid`, `error`. Null when no provider funding is expected for the payment. This describes funding into Bead, not the merchant deposit. For the merchant deposit, use `settlementId` and the [Settlement APIs](/settlement/settlements). |
| `fundingPaidAt`              | date-time or null             | When the provider funding for this payment reached `paid`. Null until then.                                                                                                                                                                                                                                                                                     |

The response envelope is `data` (array of payments for the current page), `total` (total matching records at query time), and `page` (the zero-based page returned). `PageSize` is not echoed in the response, your client should track the value it requested.

> **Fields this endpoint does not return.** `merchantLocation`, `emailReceipt`, `smsReceipt`, `trackingId`, `pageId`, `reqCurrencyId`, `payCurrencyId`, `cartItems`, `externalId`, `expiration`, `quoteExpiration`, `paymentAddress`, `redirectUrl`, `transactions`, `paymentNotifications`, `webhookUrls`, and `refundEmail` are not part of this endpoint's response. Several of them are returned by [Terminal Payments](/reporting/terminal-payments), which uses a different schema. Do not write one response parser and reuse it across both.

**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                                  |
| 404  | Merchant 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, up to the maximum of 100 | Balances throughput and latency                                                                                                                                                                                |
| Use `PageId` when resuming a previously read window                                            | Avoids duplicates when paginating through large ranges                                                                                                                                                         |
| Use the `updated` timestamp to detect payment changes                                          | Helps you refresh only modified rows                                                                                                                                                                           |
| Do not use `updated` to detect settlement events                                               | Assigning a settlement to a payment does not change the payment record. See [Settlement Reconciliation Patterns](/settlement/settlement-reconciliation-patterns).                                              |
| Rely on [payment webhooks](/payments/payment-webhooks) for real time state changes             | Webhooks notify you of status transitions and reclaim progress without polling. To catch up on reclaim returns, filter with `StatusCode=overpaid` and `StatusCode=underpaid` and read `refundLifecycleStatus`. |
| Read `settlementId`, `batchId`, `batchPeriod`, and `batchStatus` for settlement linkage        | Tells you which batch and settlement a payment belongs to directly from the payment record                                                                                                                     |
| Use the [Settlement APIs](/settlement/settlements) for funded amounts and payout state         | `netFundableAmount` and settlement `status` live on the settlement record, not the payment                                                                                                                     |
| Do not treat `fundingStatus` as the merchant deposit                                           | It tracks provider funding into Bead. The merchant deposit is tracked on the settlement                                                                                                                        |

**Related pages**

* [Partner Payments](/reporting/partner-payments) for portfolio level history, same response schema.
* [Terminal Payments](/reporting/terminal-payments) for device level history, different response schema.
* [Pagination and Sorting](/reporting/payment-history-concepts/pagination-and-sorting) for the shared parameter contract.
* [Payment Settlement Detail](/settlement/payment-settlement-detail) for how the settlement fields progress.
* [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status) for how to read the refund lifecycle fields.


# 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.

> **This endpoint returns a different response schema.** Terminal Payments does not return the same payment object as [Merchant Payments](/reporting/merchant-payments) and [Partner Payments](/reporting/partner-payments). The query parameters are identical across all three, but the response differs: Terminal Payments returns terminal and device oriented fields such as `trackingId`, `pageId`, `reqCurrencyId`, `payCurrencyId`, `emailReceipt`, `smsReceipt`, `cartItems`, `externalId`, and `merchantLocation`, and it does **not** return `batchPeriod`, `locationId`, `locationName`, `merchantId`, `tenderType`, `transferType`, `originalSenderAddress`, or the refund lifecycle fields. Do not write one response parser and reuse it across all three scopes.

For settlement lifecycle status, use the `settlementId`, `batchId`, and `batchStatus` fields on each payment together with the [Settlement APIs](/settlement). Note that `batchPeriod` is not available at terminal scope, read the payment from merchant or partner scope if you need it.

**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 accepts the same query parameters as Partner Payments and Merchant Payments.

| Name                | Type              | Required | Notes                                                                                                                                                                                              |
| ------------------- | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `From`              | string, date-time | Yes      | Start of the payment creation-time window, in UTC. Accepts either a full ISO 8601 timestamp (`2025-09-01T00:00:00Z`) or a date-only value (`2025-09-01`).                                          |
| `To`                | string, date-time | Yes      | End of the payment creation-time window, in UTC. Accepts either a full ISO 8601 timestamp or a date-only value, same as `From`.                                                                    |
| `Page`              | integer           | Yes      | Zero based page number. The first page is `0`, not `1`. Default `0`.                                                                                                                               |
| `PageSize`          | integer           | Yes      | Items per page. Default `50`, maximum `100`.                                                                                                                                                       |
| `StatusCode`        | array of strings  | No       | One or more of `created`, `processing`, `completed`, `underpaid`, `overpaid`, `fullyRefunded`, `partiallyRefunded`, `expired`, `invalid`, `cancelled`                                              |
| `TenderType`        | array of strings  | No       | Filter by one or more tender types. See [Tender Types](/reference-guide/enumerations-and-schemas/tender-types). Accepted as a filter, but `tenderType` is not returned on this endpoint's records. |
| `TrackingId`        | string            | No       | Filter by a specific tracking identifier                                                                                                                                                           |
| `PageId`            | string            | No       | Opaque cursor for continuation, echoed from a prior response                                                                                                                                       |
| `ReqCurrencyId`     | integer           | No       | Requested currency id filter                                                                                                                                                                       |
| `PayCurrencyId`     | integer           | No       | Payment currency id filter                                                                                                                                                                         |
| `CustomerId`        | string            | No       | Filter by customer id. Exact match.                                                                                                                                                                |
| `DescriptionSearch` | string            | No       | Case sensitive text search over the description                                                                                                                                                    |
| `PaymentCode`       | string            | No       | Filter by payment code. Case-insensitive substring match.                                                                                                                                          |
| `Reference`         | string            | No       | Filter by merchant reference such as invoice number. Exact, case-sensitive match.                                                                                                                  |
| `LocationName`      | string            | No       | Accepted but has no effect at this scope. See the callout below.                                                                                                                                   |
| `TerminalName`      | string            | No       | Accepted but has no effect at this scope. See the callout below.                                                                                                                                   |
| `SortBy`            | string            | No       | Field to sort by. Allowed values: `statusCode`, `expiration`, `quoteExpiration`, `created`, `updated`.                                                                                             |
| `SortDirection`     | string            | No       | `asc` or `desc`                                                                                                                                                                                    |

> **`LocationName` and `TerminalName` do not filter at this scope.** Both are accepted and return HTTP 200, but they have no effect on the result set: a value that matches nothing returns the same records as an unfiltered request. Confirmed by direct testing. This differs from [Merchant Payments](/reporting/merchant-payments) and [Partner Payments](/reporting/partner-payments), where both parameters filter as expected. Do not rely on either one here. This endpoint is already scoped to a single terminal, so filter client-side if you need to narrow further.

> **Parameter casing:** all query parameters on this endpoint are capitalized (`From`, `To`, `Page`, `PageSize`, and so on).

> **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](/reporting/payment-history-concepts/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",
      "externalId": null,
      "terminalId": "term_12345678",
      "terminalName": "Front Counter 1",
      "paymentCode": "PMT-10452",
      "batchId": null,
      "settlementId": null,
      "batchStatus": "open",
      "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,
      "cartItems": [],
      "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"
        }
      },
      "created": "2025-09-01T14:22:11.015Z",
      "updated": "2025-09-01T14:22:12.102Z"
    }
  ],
  "total": 1,
  "page": 0
}
```

**Field reference**

| Field                            | Type              | Description                                                                                                                                                                                |
| -------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`                             | string or null    | Unique payment identifier                                                                                                                                                                  |
| `externalId`                     | string or null    | External identifier supplied by the integrator, when provided                                                                                                                              |
| `terminalId`, `terminalName`     | string            | Terminal that processed the payment                                                                                                                                                        |
| `paymentCode`                    | string            | Short code for user facing references                                                                                                                                                      |
| `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.                                                                                                                          |
| `batchStatus`                    | string or null    | Status of the batch containing this payment: `open`, `closed`, or `hold`. This is a batch state, not a settlement state.                                                                   |
| `trackingId`                     | string or null    | Pollable tracking identifier for in flight payments                                                                                                                                        |
| `pageId`                         | string or null    | Opaque continuation id that can be echoed to the `PageId` query parameter                                                                                                                  |
| `statusCode`                     | string            | Current payment status. See [Payment Statuses](/payments/payment-statuses).                                                                                                                |
| `reqCurrencyId`, `payCurrencyId` | integer           | Currency identifiers for requested and paid amounts                                                                                                                                        |
| `amounts`                        | object            | `requested`, `paid`, `settleableAmount`, and `partnerFees`                                                                                                                                 |
| `reference`, `description`       | string or null    | Merchant supplied metadata fields                                                                                                                                                          |
| `customerId`                     | string or null    | Merchant supplied customer id. A string at terminal scope, unlike merchant and partner scope where it is an object.                                                                        |
| `emailReceipt`, `smsReceipt`     | boolean           | Whether a receipt was sent by email or SMS                                                                                                                                                 |
| `cartItems`                      | array             | Line items associated with the payment, when supplied                                                                                                                                      |
| `expiration`, `quoteExpiration`  | date-time or null | Payment and quote expiry timestamps                                                                                                                                                        |
| `paymentAddress`                 | string or null    | Destination address for the payment, where applicable to the tender                                                                                                                        |
| `redirectUrl`                    | string or null    | Redirect target after payment completion, when configured                                                                                                                                  |
| `transactions`                   | array             | On chain transactions associated with the payment                                                                                                                                          |
| `conversions`                    | array             | Currency conversion details, when applicable                                                                                                                                               |
| `paymentNotifications`           | array             | Notification records for the payment                                                                                                                                                       |
| `webhookUrls`                    | array of strings  | Webhook targets registered for the payment                                                                                                                                                 |
| `refundEmail`                    | string or null    | Email used for refund claims                                                                                                                                                               |
| `merchantLocation`               | object or null    | `id`, `name`, and `address` for the location                                                                                                                                               |
| `created`, `updated`             | date-time or null | ISO 8601 timestamps in UTC. Declared nullable at this scope, unlike merchant and partner scope where both are always present. They were populated in testing, but handle null defensively. |

The response envelope is `data`, `total`, and `page`. `PageSize` is not echoed in the response.

> **Fields this endpoint does not return.** `batchPeriod`, `locationId`, `locationName`, `merchantId`, `tenderType`, `transferType`, `originalSenderAddress`, `refundLifecycleStatus`, `refundInitiatedAt`, `refundCompletedAt`, and `refundFailureReason` are not part of this endpoint's response. Use [Merchant Payments](/reporting/merchant-payments) or [Partner Payments](/reporting/partner-payments) when you need any of them.

> **There is no `paymentSettlementStatusCode` field.** It is not returned here and is not accepted as a filter. For settlement status, use `settlementId`, `batchId`, and `batchStatus` on the payment, together with [Settlements](/settlement/settlements), [Batches](/settlement/batches), and [Deposits](/settlement/deposits).

**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, up to the maximum of 100.
* Use `SortBy=created` with `SortDirection=desc` to see the most recent payments first in device diagnostics.
* Handle `created` and `updated` as nullable at this scope. Merchant and partner scope always return both.
* Do not use `LocationName` or `TerminalName` here. Both are accepted but neither filters at this scope. Narrow client-side instead.
* Combine terminal scope with partner or merchant scope when investigating issues that may affect multiple devices, and expect a different field set from each.
* Use `settlementId`, `batchId`, and `batchStatus` 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](/reporting/partner-payments) and [Merchant Payments](/reporting/merchant-payments).


# Split Funding

The Split Funding Program lets a partner decide, per payment, exactly how much a merchant keeps. The consumer pays the full ticket, the merchant is settled the agreed amount with no transaction fee deducted, and Bead withholds the difference and later pays it to the partner.

Use this page when a merchant is enrolled in the Split Funding Program. For standard payment creation, see [Create Payment](/payments/create-payment).

> **Read the next two sections before you scope the work.** This program is normally operated with a consumer fee, and it places real obligations on you rather than on Bead. Confirm you can meet all four requirements in [What this program requires of you](#what-this-program-requires-of-you) before you commit to an integration.

### Where the money for Bead's fee comes from

Start here, because it determines whether the program is usable for your merchant at all.

The merchant is settled `merchantPortion` exactly, with no transaction fee deducted. Every cost Bead charges is therefore recovered from the partner portion. That means the partner portion has to be large enough to cover Bead's per-payment fee on every tender you offer, plus a share of the settlement fee.

There are only two places that money can come from.

| Approach                          | What happens                                                                                                                        | Consequence                                                                                                 |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **A consumer fee you add on top** | `total` is the merchant's price plus your fee. `merchantPortion` is the price the merchant billed. The partner portion is your fee. | The merchant is settled the exact amount they billed. This is the intended use of the program.              |
| **The merchant's own margin**     | `total` is the merchant's price. You set `merchantPortion` below that price and the merchant absorbs the difference.                | Workable, but the merchant is no longer receiving exact settlement, which is the reason the program exists. |

There is no third option. If the partner portion does not cover Bead's fee on a tender, that tender is removed from the payment page. If it covers none of the offered tenders, the payment is rejected outright. See [Coverage is checked when the payment is created](#coverage-is-checked-when-the-payment-is-created).

In practice this makes the Split Funding Program a consumer fee program. You can run it without one, but only by reducing what the merchant keeps.

### What this program requires of you

Because the fee is collected from the consumer, in your environment, under your disclosure, the obligations sit with you. Bead receives computed dollar amounts and processes them.

**Four requirements. If your solution cannot meet all four, this program is not a fit.**

**1. You understand consumer fee law and can apply it.** State-level surcharge and convenience fee rules differ, some states prohibit the practice outright, and several impose pricing-transparency rules that reach beyond card tenders. Platform terms add a second layer that varies by tender. You are the party making the determination of what a consumer may be charged, for which tender, in which state. See [Consumer Fee Passthrough for Wallet and Crypto Tenders](/faqs-and-troubleshooting/payments-faqs/consumer-fee-passthrough-for-wallet-and-crypto-tenders).

**2. Your solution supports a tender-based consumer fee.** This is a hard technical requirement, not a recommendation. Bead's buy rates differ substantially between tenders, so one flat fee applied to every tender will be overpriced on the cheap ones and underwater on the expensive ones. An underwater fee does not error: the coverage check removes those tenders from the consumer's payment page, and names them back to you on the creation response. Your fee logic must be able to vary by tender, which in turn means determining the tender in your own environment before you create the payment, or sizing for the most expensive tender you offer. Permissibility also varies by tender, so a single fee can be legally wrong as well as commercially wrong.

**3. You perform the fee calculation.** Bead does not calculate a consumer fee. There is no rate field, no percentage field and no fee schedule input anywhere on the payment request. You compute the figures and send three dollar amounts that balance to the cent. Bead can process a fee you have already calculated. It cannot derive one.

**4. You handle consumer notification and presentment.** Bead's hosted payment page does not disclose, itemize or display a consumer fee. It shows a single total. Disclosure to the consumer happens in your checkout, before the payment is created, in whatever form the applicable rules require.

> **This is why the program suits solution providers and resellers who already operate consumer fee collection.** It is not a capability a merchant can switch on, and it is not something an integrator can adopt without building the fee logic, the per-tender sizing and the disclosure around it. If you are evaluating this program, plan for that work.

> **You send dollar amounts, not percentages.** There is no rate, percentage or fee schedule anywhere on this request. You calculate the figures in your own environment and send three computed dollar amounts that must balance to the cent. Any percentage in this page describes arithmetic you perform before you call Bead.

### How it differs from standard processing

On standard processing, Bead calculates the transaction fee from the merchant's fee configuration and deducts it from the merchant's settlement.

On the Split Funding Program:

* Your integration sends the exact amount the merchant should keep.
* Bead settles that amount, with no transaction fee applied. The merchant's fee configuration is not consulted at all.
* The remainder is withheld at settlement and paid to the partner on a schedule, net of Bead's fee.
* The merchant is never billed afterwards and never sees a later deduction.

Enrolled merchants can still send ordinary payments. Omit the split funding block and the payment is processed normally. Mixed traffic on one merchant is supported and the two kinds settle side by side, with one consequence for settlement fees described in [Settlement behavior](#settlement-behavior). Those ordinary payments compensate you through your standard processing arrangement rather than through the distribution, so a merchant running both kinds is compensated through both routes, with every transaction counted once.

### Two destinations, fixed by role

This is the most important boundary on the program, and the one most often misread, because the term "split funding" carries assumptions from card processing that do not apply here.

**A payment can fund exactly two accounts, and which two is not yours to choose:**

| Destination                    | What it receives                                            |
| ------------------------------ | ----------------------------------------------------------- |
| The merchant's account on file | The merchant portion, in full, with nothing deducted        |
| Your partner account on file   | The withheld partner portion, with every Bead cost deducted |

"On file" means the bank account already held on that entity's record at Bead. For the merchant that is the account established at boarding. For you it is the account on your designated representing merchant record. Neither can be overridden on a payment.

**You direct the amounts. You do not direct the destinations.** Specifically, none of the following is possible today:

* Naming a payee, account or routing detail on the payment.
* Splitting a payment three or more ways.
* Funding a third party who is neither the merchant nor you.
* Registering a sub-merchant to receive part of a payment.
* Sending a merchant's proceeds to a second account belonging to that merchant, for example settling the sale to a retail account and a fee to an operating account.
* Sending your portion anywhere other than your own account on file.

If an integration needs any of those, this program is not the mechanism. Raise it with your Bead integration contact rather than trying to model it with the amounts.

### The three amounts

Every split funding payment carries three amounts.

| Amount            | Description                                                               |
| ----------------- | ------------------------------------------------------------------------- |
| `total`           | What the consumer pays. Must exactly equal `requestedAmount`.             |
| `merchantPortion` | The exact amount settled to the merchant. No transaction fee is deducted. |
| `partnerPortion`  | The amount withheld at settlement for later distribution to the partner.  |

All three are computed dollar amounts, limited to two decimal places.

#### The rule that must always hold

`merchantPortion + partnerPortion` must exactly equal `total`.

There is no remainder and no rounding tolerance. A payment that breaks this is rejected with `400 Bad Request`.

`total` must also equal `requestedAmount`. If your integration changes the ticket amount, all four values have to move together.

### Bead's split funding fee

Bead deducts a fee from the withheld partner portion before paying the partner. It is calculated per payment and never touches `merchantPortion`:

```
partnerNet = partnerPortion - splitFundingFee - depositFee
```

`depositFee` is the settlement fee recovered from the partner portion. Both deductions are reported under those names on the distribution summary. See [Split Funding Reports](/split-funding/reports).

**There is no standard rate.** The fee is the partner's own contracted rate with Bead, the same rate card that governs their other business, so it differs from partner to partner and can differ per payment method. It resolves from the partner record, not from the merchant's fee configuration, and it cannot be sent on the payment. Your rates are set out in your executed reseller agreement and are also retrievable from the partner fee-schedule endpoint.

The per-payment fee has two components, both deducted from the partner portion:

| Component           | Applied to   |
| ------------------- | ------------ |
| Percentage          | `total`      |
| Per-transaction fee | Each payment |

**The percentage applies to `total`, the full amount the consumer paid.** It is not calculated on `merchantPortion`. The deduction still comes out of the partner portion alone, so the merchant is unaffected either way: a larger total means a larger fee drawn from the withheld amount.

**The fee is calculated at assessment, not at payment creation.** The rate is read as of the payment's capture date, so the result is stable, but the fee is not present on the payment record at transaction time. It is reported afterwards as `splitFundingFee`, on both the transaction report and the distribution summary.

**Rates differ by tender, and that difference is what drives requirement 2 above.** Pull your own fee schedule and compare the cheapest tender you offer against the most expensive one before you design your fee logic. The gap is normally large enough that a single flat consumer fee cannot serve both.

#### Coverage is checked when the payment is created

At payment creation Bead prices every tender the payment page would offer against your contracted rate, and compares each against the partner portion you sent.

* Tenders the partner portion covers are offered on the page.
* Tenders it cannot cover are left off the page, and named on the response.
* If no offered tender is covered, the payment is rejected with `400 Bad Request`. The error prices every offered tender, percentage and per-item fee, so you can resize for the one you want.

The tender is not known at creation, because the consumer selects it on the hosted page. Pricing every candidate tender and narrowing the page is how the check stays exact. You can still narrow `tenderTypes` yourself per payment.

**The creation response names every tender it left off.** `unavailableTenders` is returned on the payment creation response and is empty on an ordinary payment, and on a split funding payment where every candidate tender was covered. Each entry carries:

| Field             | Description                                                                                                    |
| ----------------- | -------------------------------------------------------------------------------------------------------------- |
| `tenderType`      | The tender left off the page.                                                                                  |
| `reason`          | `feeNotCovered` or `noContractedRate`.                                                                         |
| `fee`             | What Bead would have extracted from the partner portion on this tender. Null when there is no contracted rate. |
| `transactionRate` | The percentage rate that was applied. Null when there is no contracted rate.                                   |
| `perItemFee`      | The per-transaction fee that was applied. Null when there is no contracted rate.                               |

Read this on every split funding payment rather than waiting for the distribution. A non-empty array means the consumer saw fewer ways to pay than you intended, and `fee` tells you exactly what the partner portion would have needed to cover.

**A missing contracted rate excludes the tender.** A tender with no rate row for your partner record is priced as uncovered and left off the page, reported as `noContractedRate` with null amounts. It is not treated as free. If you see this reason, the rate is missing from your fee schedule rather than your consumer fee being too small; raise it with your Bead contact.

Equality passes. A partner portion that exactly covers a tender's fee keeps that tender, and a partner on zero rates with a zero per-item fee never loses a tender or receives a rejection, including for a zero partner portion.

**What this check cannot see.** It covers per-payment costs only, the percentage and the per-transaction fee. Settlement fees are charged per settlement event and the number of payments in a settlement is unknown when a payment is created, so settlement fee coverage is not evaluated here. It resolves at distribution assessment instead, described in [Partner distributions](#partner-distributions).

#### Sizing against the floor

Because the per-transaction fee is flat and does not scale with ticket size, low-value tickets need a proportionally larger partner portion for you to earn anything. Settlement fees add a second flat cost that is shared across however many payments land in a settlement.

Where `c` is the consumer fee rate you charge on the merchant's price, `p` is your percentage rate with Bead, `f` is your per-transaction fee and `s` is your settlement fee:

```
flatCostPerPayment         = f + (s / paymentsPerSettlement)
break-even merchantPortion = flatCostPerPayment / (c - p - (p * c))
```

The `p * c` term is there because Bead's percentage is charged on `total`, which includes the consumer fee you added. Sizing against `c - p` alone understates the floor.

**A consumer fee set equal to your Bead rate does not break even.** Because the percentage is charged on the grossed-up total, your fee has to exceed your rate divided by one minus your rate before any flat cost is considered.

**The floor depends on volume, not only on ticket size.** A merchant settling one payment a day carries the whole settlement fee on that payment. The same fee spread across fifty payments is negligible. Model the floor for each merchant against the number of payments they actually put through in a settlement period rather than a single headline figure.

> **Compensation on this traffic is paid to you separately.** Split funding transactions compensate you through the withheld partner portion, paid as its own monthly distribution. The same transactions are not also counted in your standard processing compensation, because the merchant was charged no fee on them and there is nothing there to calculate from. Each transaction is compensated once, through one route or the other. If you move existing traffic onto this program, your compensation on that traffic arrives through the distribution from that point forward. See [Residual Reporting](/settlement/residual-reporting).

#### Expect your distribution to differ from what this traffic earned you before

Model this before you move traffic onto the program. On standard processing your compensation derives from fees Bead charged your merchant and reaches you at month end. Here the merchant is charged nothing, so your compensation is the consumer fee you collected, less Bead's fee and the settlement fees, paid as a separate distribution. The two are calculated from entirely different things and will not be the same size. Work it through with your own rates rather than assuming a like for like replacement.

### Worked example

A split funding payment where the merchant keeps less than the full ticket, with the difference withheld for the partner.

| Step                                        | What it represents                                      |
| ------------------------------------------- | ------------------------------------------------------- |
| Consumer pays (`total`)                     | The full ticket amount                                  |
| Merchant is settled (`merchantPortion`)     | The agreed amount, no transaction fee deducted          |
| Withheld for the partner (`partnerPortion`) | `total` minus `merchantPortion`                         |
| Bead's split funding fee                    | Priced on `total`, deducted from the partner portion    |
| Settlement fees                             | Deducted from the partner portion, per settlement event |
| Partner receives (`partnerNet`)             | What remains                                            |

The merchant sees exactly `merchantPortion` on their daily settlement, regardless of what Bead's fee ends up being.

### Collecting a consumer fee alongside this program

**Bead has no consumer fee capability.** There is no fee field on the payment, no rate configuration, and nothing in the API that calculates, displays or collects a consumer fee. Bead can process a fee you have already calculated, folded into the amounts you send. It cannot derive one. Sizing, disclosing and presenting the fee is entirely your own work in your own checkout. The [Consumer Fee Passthrough](/faqs-and-troubleshooting/payments-faqs/consumer-fee-passthrough-for-wallet-and-crypto-tenders) page is guidance on how to do that and on the legal considerations. It is not a feature you enable.

The pattern: calculate your consumer fee yourself, per tender, and fold it into `total`. Set `merchantPortion` to the exact price the merchant billed, with nothing deducted. Set the partner portion to the fee amount. The merchant is settled precisely what they billed, with no net due to fees. The withheld amount is paid out to you later, net of Bead's fee and the settlement fees, on the cadence described in [Partner distributions](#partner-distributions).

> **If you use Bead's hosted page for tender selection, read this before you size a fee.** When the consumer chooses their tender on a Bead-hosted page, you do not know the tender at the point you would normally calculate and disclose a tender-specific consumer fee. This program cannot resolve that for you, because the three amounts are fixed when the payment is created. It is also the direct tension with requirement 2 above. Your options are to determine the tender in your own environment before calling Bead, or to size a single fee that covers every tender you offer and accept thinner margin on the cheaper ones. A fee sized for the cheapest tender will leave you underwater on the most expensive one, and the coverage check will drop those tenders from the page. A separate Bead initiative is evaluating consumer fee support for hosted-page flows. It is not part of this program and is not available today.

**Leave headroom.** Sizing a consumer fee to break even against Bead's fee means earning nothing and losing tenders from the page on any rounding. Size for the most expensive tender you support, or size per tender.

### Before you start

A merchant must be enrolled before it can accept a split funding block on a payment. Enrollment is a reviewed change to the merchant record, submitted and approved through [Update Merchant](/entity-management/merchant-management/update-merchant).

There is no field for this on merchant application submission. A merchant must be boarded first, then enrolled with the update-request call below. Nothing stops you from submitting the enrollment request immediately after boarding completes, since it is a partner-self-service call, but it is always a separate second step.

Use a partner API key. You can enroll any merchant in your own hierarchy, and you can approve your own request. Terminal API keys cannot make this change and return `403 Forbidden`.

**Step 1. Request the change.** Note the request id in the response.

```json
POST /Merchants/{merchantId}/update-requests

{
  "submit": true,
  "precedence": 100,
  "justification": "Enrolling in the split funding program",
  "expiresAt": "2026-12-31T23:59:59Z",
  "changes": {
    "setSplitFundingEnabled": true
  }
}
```

**Step 2. Approve it.** The change applies immediately on approval.

```json
POST /Merchants/{merchantId}/update-requests/{requestId}/approve

{
  "reason": "Approved for split funding program"
}
```

**Step 3. Confirm.** [Get Merchant](/entity-management/merchant-management/get-merchant) returns the enrollment flag as `isSplitFundingEnabled`, on the flat `merchant` node of the response rather than inside `details`. It reads `true` once the change has applied.

> **The old field names are not aliased.** `setRebateProgramEnabled` and `isRebateProgramEnabled` were renamed to `setSplitFundingEnabled` and `isSplitFundingEnabled`. There is no "Program" in either new name. A request that still sends `setRebateProgramEnabled` is dropped by the deserializer and returns a successful `200` with nothing enabled, so step 3 is the only reliable confirmation that enrollment applied.

To remove a merchant from the program, send the same request with the flag set to `false`. That stops new split funding payments. Anything already taken still pays out to the partner as normal, and in-flight settlements and assessed distributions are unaffected.

> **`expiresAt` does two different things here, and neither is the one integrators expect.**
>
> It does not time box the enrollment. Setting `expiresAt` on the update request does not revert the flag. Use an explicit `false` request to remove a merchant from the program.
>
> Omitting it blocks your later requests. The overlap check considers only `approved` and `applied` requests, and treats a null `expiresAt` as an open-ended window. A second request at the same `precedence` therefore returns `409 Conflict` indefinitely, and that includes the `false` request above. Set an `expiresAt` on every enrollment request, or submit the later request at a higher `precedence`.

Separately, confirm the partner payout setup described in [Partner distributions](#partner-distributions) before the first split funding payment is taken. That setup is not part of enrollment and is not validated here.

### Creating a split funding payment

Add a `splitFunding` object to the normal payment creation request. The endpoint and all other fields are unchanged.

> **`rebate` is no longer accepted, and sending it fails silently.** `splitFunding` is the only name the request binds. A request that still sends `rebate` returns `200 OK` with the payment created as an ordinary payment: the field is dropped, nothing is withheld for the partner, and no error is returned. There is no way to detect this from the response. If your integration predates the rename, switch it to `splitFunding` and confirm against a [Split Funding Reports](/split-funding/reports) transaction row, where a genuine split funding payment shows a `partnerPortion` greater than zero. Inside the wrapper the amount field is `partnerPortion` only, with no `targetRebate` alias.

Endpoint:

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

`POST /Payments/crypto` is deprecated but still supported and accepts the same body, including `splitFunding`. Use `POST /Payments` for new work.

Headers:

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

Request body:

```json
{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 100.00,
  "reference": "ORDER-4821",
  "splitFunding": {
    "total": 100.00,
    "merchantPortion": 90.00,
    "partnerPortion": 10.00
  }
}
```

The response is the standard payment creation response, plus `unavailableTenders`. See [Create Payment](/payments/create-payment) for the full shape and for the terminal type rules that also apply to split funding payments, including the `customer` and `refundEmail` requirements on virtual terminals.

#### Field reference

`splitFunding` is an optional object. It is only accepted for enrolled merchants. Omit it entirely for a standard payment.

`splitFunding.total` is the total amount the consumer pays. Must exactly equal `requestedAmount`. It is also the base Bead's percentage is charged on.

`splitFunding.merchantPortion` is the exact amount to settle to the merchant daily. Settled in full with no transaction fee deducted.

`splitFunding.partnerPortion` is the amount withheld at settlement for later distribution to the partner. Must exactly equal `total` minus `merchantPortion`.

### Why a split funding payment is rejected

All of the following return `400 Bad Request`.

| Condition                                                      | What to check                                                                                  |
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `merchantPortion + partnerPortion` does not equal `total`      | The core rule. Look for a rounding error, for example `90.00 + 9.99` against a `100.00` total. |
| The partner portion covers no offered tender                   | The coverage check. The error prices every offered tender so you can resize.                   |
| `total` does not equal `requestedAmount`                       | Usually the ticket amount changed and only one of the two values was updated.                  |
| Only one or two of the three amounts sent                      | All or nothing. Send all three, or omit the object entirely.                                   |
| A split funding block sent for a merchant that is not enrolled | Confirm the enrollment flag on the merchant record.                                            |
| `merchantPortion` greater than `total`                         | The values are most likely transposed.                                                         |
| More than two decimal places on any amount                     | Often a percentage calculated on your side and not rounded before sending.                     |

Sending `rebate` instead of `splitFunding` is not on this list, because it is not rejected. See the callout in [Creating a split funding payment](#creating-a-split-funding-payment).

If you calculate `merchantPortion` or the partner portion from a percentage, round to two decimals and then derive the third value by subtraction rather than by a second percentage calculation. Deriving both independently is the most common cause of a failed identity check.

> **Error responses on this endpoint are not uniform, and two of them are bare strings.** Most validation failures return the standard error body, but both the enrollment check and the fee coverage rejection return a plain text message at `text/plain` rather than the structured error shape the endpoint declares. Match on the HTTP status rather than on the response shape, and log the body. The coverage rejection's message is your only diagnostic for resizing the partner portion, so a client that discards a body it cannot parse as JSON is left with a `400` and no detail.

#### A quote for a tender that was not offered

A tender dropped from the page by the coverage check is also refused at quote time. Request-quote and pending-transaction return `409 Conflict` with `TENDER_NOT_REQUESTED`. This is what makes the narrowed page binding for direct API callers as well as for the hosted page.

#### What is not returned

The amounts you send are not echoed back on the payment creation response, the payment status endpoints, or [Payment Statuses](/payments/payment-statuses) tracking. Bead's fee is not returned on any payment level response either.

The creation response does report which tenders were left off the page, in `unavailableTenders`, but it does not list the tenders that were offered.

The amounts do appear on the refund validation responses, under the older `rebate` and `targetRebate` names. No Bead tender supports refunds today, so this does not arise in practice.

Keep your own record of what you sent. To see the amounts and the fee after the fact, use the [Reporting](#reporting) endpoints below.

Payment webhooks are unchanged for split funding payments, and partner distributions do not emit webhooks.

### Settlement behavior

* The merchant is settled `merchantPortion` on the normal daily cycle, with no transaction fee.
* Nothing is billed back to the merchant later. Collection happens by withholding at settlement.

How the per-deposit settlement fee is handled depends on the composition of that day's settlement.

| Settlement composition                       | Merchant's deposit        | Recovered from the partner portion |
| -------------------------------------------- | ------------------------- | ---------------------------------- |
| Every payment is a split funding payment     | Full net, no fee deducted | Yes, one fee per settlement event  |
| A mix of split funding and ordinary payments | Fee deducted as usual     | No                                 |
| No split funding payments                    | Fee deducted as usual     | Not applicable                     |

**On a split-funding-only day the merchant receives their portion exactly**, with no fee-schedule lookup on the deposit at all. Neither a missing fee schedule nor a below-fee hold can delay that merchant's money.

**On a mixed day the deposit is charged on the ordinary rules**, and nothing is recovered from the partner portion for it. If your merchant also takes ordinary payments, be precise with them about this: the exact-settlement outcome applies to days where their settlement holds split funding payments alone.

The fee is charged once per settlement event, not once per payment, and not once per transfer when a daily deposit limit splits a deposit into several transfers.

The settlement and batch reports carry two informational fields for split funding activity:

| Field                          | Description                                                                          |
| ------------------------------ | ------------------------------------------------------------------------------------ |
| `partnerPortion`               | Sum of the partner portion across split funding payments in the settlement or batch. |
| `splitFundingTransactionCount` | Number of split funding payments in the settlement or batch.                         |

**Both fields sit on the settlement and batch report rows, not on the settlement or batch records themselves.** The settlement and batch responses returned by the endpoints on [Settlements](/settlement/settlements) and [Batches](/settlement/batches) do not carry either field. Read them from the settlement and batch reports, where they are also available as selectable CSV columns. Merchants with no split funding activity show zero.

These are aggregates for visibility. They are not the basis of the partner payout. See the caution in [Residual Reporting](/settlement/residual-reporting).

### Partner distributions

The withheld partner portion is paid to the partner as a single periodic bank transfer covering all of that partner's merchants together, not one payment per merchant or per transaction. It is sent to the partner account on file described in [Two destinations, fixed by role](#two-destinations-fixed-by-role), and it is a separate payment from any standard processing compensation you receive from Bead.

* The amount is the sum of the partner portion across every qualifying payment in the run, less the per payment fee and the settlement fees.
* Distributions to the partner account run monthly.

**Settlement fee coverage resolves here, not at payment creation.** A distribution whose total partner net is not positive is created directly into `blocked`, alerted internally, and never paid until an operator resolves it. The payout step re-checks the payable amount as a safety net.

Four things must be in place before a partner can be paid, and none of them are checked at enrollment. They are checked when the payout runs, which means a gap here surfaces at the first payout rather than at setup:

1. The partner has a designated representing merchant record, which is where the funds are sent and where the partner's contracted rates are read from.
2. That merchant has complete bank details, with an account type of `Checking` or `Savings`.
3. That merchant has fee rules for every payment method the enrolled merchants will use. Bead's fee is read per payment method, so a payment on a method with no configured rate cannot be priced, and that tender is left off the payment page at creation.
4. That merchant is active.

Confirm all four with your Bead contact before the first split funding payment is taken.

#### Refunds (reserved)

No Bead tender currently supports a refund, so this does not come up in practice today. The behavior below is defined for when a tender that supports refunds is added, or another refund-like event occurs:

* A split funding payment that is refunded, in full or in part, before the partner payout would be excluded from that payout automatically.
* A refund that lands after the payout has gone out would be corrected on the partner's next distribution.
* A refund returns the full amount the consumer paid, which is `total`, not `merchantPortion`. If that amount included a consumer fee, the consumer is made whole for the fee as well, and recovering it is a matter between you and your merchant.

### Reporting

Two report families cover split funding activity. Both support pagination, sorting, and column selectable CSV export, and both are callable with your partner API key for your own partner or any partner beneath it. See [Split Funding Reports](/split-funding/reports) for the full reference.

These reports are the only place the amounts and Bead's fee are readable after payment creation.

#### Distribution status values

`status` and `blockedReason` are both partner readable. They are returned on the base distribution summary report, on a separate top level `distributions[]` array rather than on the per merchant rows, and they do not appear on the period summary, the account summary or the CSV. See [Split Funding Reports](/split-funding/reports) for where they sit in the response.

| Status      | Meaning                                          |
| ----------- | ------------------------------------------------ |
| `assessed`  | Calculated and awaiting payout.                  |
| `paying`    | Payout in progress.                              |
| `paid`      | Paid to the partner.                             |
| `blocked`   | Held for Bead review. Not retried automatically. |
| `cancelled` | Voided.                                          |

A distribution is held at `blocked` rather than paid when something about the period or the payout account prevents it. The usual causes are a partner net for the period that is not positive, which is the check that covers settlement fee shortfalls that the creation-time coverage check cannot see, or a partner account on file that is not ready to receive funds, for example a missing or inactive representing merchant record, incomplete bank details, or an unsupported bank account type. See [Two destinations, fixed by role](#two-destinations-fixed-by-role) for what the payout account has to be.

`blockedReason` accompanies `status` on the same `distributions[]` array and names the specific check that held the distribution. There are eight values. Five of them describe the period's arithmetic or the payout account on file:

| Reason                         | Meaning                                                                                                                                                  |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `partnerNetNotPositive`        | The distribution as a whole netted to zero or less. This is the check that covers settlement fee shortfalls the creation-time coverage check cannot see. |
| `representingMerchantNotFound` | No designated representing merchant record was found for the partner, so there is no account to send funds to.                                           |
| `representingMerchantInactive` | The representing merchant record is not active.                                                                                                          |
| `missingBankDetails`           | The representing merchant record does not carry complete bank details.                                                                                   |
| `unsupportedBankAccountType`   | The bank account on file is not an account type the payout supports. The supported types are `Checking` and `Savings`.                                   |

The three `representingMerchant` reasons and `missingBankDetails` all say the same thing in different ways: the partner account on file is not ready to receive funds.

The remaining three values, `originationRejected`, `originationAmbiguous` and `payoutReturned`, are reported against the payout transfer itself rather than against the period's arithmetic or the account record.

If a distribution is blocked, contact your Bead integration contact. Blocked distributions are not retried automatically.

### Troubleshooting

**A payment is rejected with `400`** Work through the table in [Why a split funding payment is rejected](#why-a-split-funding-payment-is-rejected). The identity check, the coverage check and the `total` versus `requestedAmount` check account for most cases.

**Tenders are missing from the payment page** Read `unavailableTenders` on the creation response. It names each tender and why: `feeNotCovered` means the partner portion did not cover Bead's fee on that tender, and `fee` tells you what it would have needed; `noContractedRate` means your fee schedule has no rate for it. Rates differ substantially by tender, which is why a single flat consumer fee rarely works. See [Coverage is checked when the payment is created](#coverage-is-checked-when-the-payment-is-created).

**A quote returns `409 TENDER_NOT_REQUESTED`** The tender was not on the page for that payment, either because you narrowed `tenderTypes` yourself or because the coverage check dropped it.

**An enrollment request returns `409 Conflict`** An earlier `approved` or `applied` update request with no `expiresAt` is treated as covering an open-ended window, so any later request at the same `precedence` conflicts with it indefinitely. This includes the `setSplitFundingEnabled: false` request that would remove the merchant from the program. Set an `expiresAt` on every enrollment request, or submit the later one at a higher `precedence`. See [Update Merchant](/entity-management/merchant-management/update-merchant).

**Enrollment returned `200` but nothing is enabled** Check the field name you sent. The old `setRebateProgramEnabled` is not aliased to `setSplitFundingEnabled`, and an unrecognized field is dropped by the deserializer rather than rejected, so the request still returns `200`. Read the merchant back and check `isSplitFundingEnabled` on the flat `merchant` node.

**The payment succeeded but nothing was withheld** Check the wrapper name. `rebate` is no longer bound and is dropped silently, so the payment is created as an ordinary one with a `200`. Confirm the request sends `splitFunding`, then check a [transaction report](/split-funding/reports) row for the payment: a genuine split funding payment shows a `partnerPortion` greater than zero.

**`splitFundingFee` or `partnerNet` is null on the transaction report** No rate resolved for that payment's tender, which is the same condition that defers the payment at distribution assessment. `partnerNet` is null whenever the fee is. Raise the missing rate with your Bead contact.

**The merchant's settlement does not match `merchantPortion`** Confirm the payment carried a split funding block and that the merchant was enrolled at the time of creation. A payment created before enrollment is processed as a standard payment. If the settlement mixed split funding and ordinary payments, the deposit carried the settlement fee on the ordinary rules; see [Settlement behavior](#settlement-behavior). Check the [transaction report](/split-funding/reports) for the payment to see the amounts as they were recorded.

**The merchant is not getting a deposit at all** Check what the merchant portion was set to. If it is set at or near the full ticket with no consumer fee added on top, there is nothing left to cover Bead's costs, and those payments will have been rejected or had their tenders removed at creation. See [Where the money for Bead's fee comes from](#where-the-money-for-beads-fee-comes-from).

**Enrollment returns `403`** You are using a terminal API key. Enrollment requires a partner API key with the merchant in its hierarchy.

**A partner has not been paid when expected** Check the four prerequisites in [Partner distributions](#partner-distributions) first. A missing representing merchant, incomplete bank details, or an inactive merchant record are the most common causes, and none of them fail at enrollment. Then check whether the distribution is `blocked` and read its `blockedReason` on the `distributions[]` array, and if it is blocked, contact your Bead integration contact.

**We want the partner portion to go somewhere else** It cannot. The two destinations are fixed by role and are not settable on a payment. See [Two destinations, fixed by role](#two-destinations-fixed-by-role).

### Next steps

* [Consumer Fee Passthrough for Wallet and Crypto Tenders](/faqs-and-troubleshooting/payments-faqs/consumer-fee-passthrough-for-wallet-and-crypto-tenders), which you should read before building against this page
* [Create Payment](/payments/create-payment) for the full payment creation contract
* [Split Funding Reports](/split-funding/reports) for transaction detail and partner distribution reconciliation
* [Settlements](/settlement/settlements) and [Residual Reporting](/settlement/residual-reporting) for reconciliation
* [Update Merchant](/entity-management/merchant-management/update-merchant) for the enrollment change


# Enable and Send a Split Funding Payment

Four calls, end to end. This page is the short path for a developer who already knows what the program is and wants to get a split funding payment through. For the rules, the obligations and the arithmetic behind the amounts, read [Split Funding](/split-funding) first.

Use a **partner API key** for calls 1 to 3 and the **terminal API key** for call 4. A terminal key cannot enroll a merchant and returns `403 Forbidden`.

## The four calls

| # | Call                                                               | Key      | What it does                                    |
| - | ------------------------------------------------------------------ | -------- | ----------------------------------------------- |
| 1 | `POST /Merchants/{merchantId}/update-requests`                     | Partner  | Requests enrollment                             |
| 2 | `POST /Merchants/{merchantId}/update-requests/{requestId}/approve` | Partner  | Applies it immediately                          |
| 3 | `GET /Merchants/{merchantId}`                                      | Partner  | Confirms `isSplitFundingEnabled` is `true`      |
| 4 | `POST /Payments`                                                   | Terminal | Creates the payment with a `splitFunding` block |

Calls 1 to 3 are one-time per merchant. Call 4 is every payment.

`POST /Payments/crypto` is deprecated but still supported and accepts the same body, including `splitFunding`. Use `POST /Payments` for new work.

## 1. Request enrollment

Always set `expiresAt`. Omitting it leaves an open-ended window that makes every later request at the same `precedence` fail with `409 Conflict`, including the request that would later remove the merchant from the program.

```json
POST /Merchants/{merchantId}/update-requests

{
  "submit": true,
  "precedence": 100,
  "justification": "Enrolling in the split funding program",
  "expiresAt": "2026-12-31T23:59:59Z",
  "changes": {
    "setSplitFundingEnabled": true
  }
}
```

Keep the request id from the response.

## 2. Approve it

You can approve your own request for any merchant in your hierarchy. The change applies on approval.

```json
POST /Merchants/{merchantId}/update-requests/{requestId}/approve

{
  "reason": "Approved for split funding program"
}
```

## 3. Confirm

```http
GET /Merchants/{merchantId}
```

Read `isSplitFundingEnabled` on the flat `merchant` node of the response, not inside `details`. It reads `true` once the change has applied.

**Do not skip this call.** The old field name `setRebateProgramEnabled` is not aliased. A request that still sends it is dropped by the deserializer and returns a successful `200` with nothing enabled, so the response to call 1 does not tell you whether enrollment worked. Step 3 is the only reliable confirmation.

## 4. Create the payment

Add a `splitFunding` block to an ordinary payment request. Everything else about the request is unchanged.

```json
POST /Payments

{
  "terminalId": "TERM-123",
  "merchantId": "MERCH-456",
  "requestedAmount": 103.50,
  "reference": "ORDER-4821",
  "splitFunding": {
    "total": 103.50,
    "merchantPortion": 100.00,
    "partnerPortion": 3.50
  }
}
```

In this shape the merchant billed $100.00, you added a $3.50 consumer fee, and the merchant is settled $100.00 exactly. The $3.50 is withheld and paid to you later, net of Bead's fee.

### The three rules the amounts must satisfy

1. `merchantPortion + partnerPortion` equals `total`, exactly. No rounding tolerance.
2. `total` equals `requestedAmount`.
3. The partner portion covers Bead's fee on at least one offered tender, or the payment is rejected.

If you calculate one portion from a percentage, round it to two decimals and derive the other by subtraction. Calculating both independently is the most common cause of a failed balance check.

## Then read the response

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

**Check `unavailableTenders` before you present the page.** A non-empty array means the consumer will see fewer ways to pay than you intended. Each entry carries `tenderType`, `reason`, `fee`, `transactionRate` and `perItemFee`.

* `feeNotCovered`, the partner portion was smaller than Bead's fee on that tender. `fee` tells you what it needed to be.
* `noContractedRate`, your fee schedule has no rate for that tender. A configuration gap, not a sizing problem. Raise it with your Bead contact.

## Three things that fail silently

These are the ones that cost integrators the most time, because none of them returns an error.

| What happens                                 | Why                                                           | How to catch it                                                                                  |
| -------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| Enrollment returns `200`, nothing is enabled | You sent `setRebateProgramEnabled`, which is no longer bound  | Call 3                                                                                           |
| Payment returns `200`, nothing is withheld   | You sent `rebate` instead of `splitFunding`, which is dropped | A [Split Funding Reports](/split-funding/reports) row showing `partnerPortion` greater than zero |
| Consumer sees fewer tenders than expected    | The partner portion did not cover Bead's fee on the rest      | `unavailableTenders` on the creation response                                                    |

## Sizing the partner portion

Bead's fee is the contracted tender rate applied to `total`, the full amount the consumer paid, plus the per-item fee. Both come out of the partner portion.

Because the rate is charged on the total rather than on the merchant's price, a consumer fee set equal to your Bead rate does not break even. The percentage your fee has to exceed is your rate divided by one minus your rate. Rates also differ sharply between tenders, so a single flat fee across every tender will be underwater on the expensive ones.

Full detail, including the volume-dependent floor on small tickets, is in [Sizing against the floor](/split-funding#sizing-against-the-floor).

## What this page does not cover

Read the [Split Funding](/split-funding) page in full before you build. In particular:

* [What this program requires of you](/split-funding#what-this-program-requires-of-you), four obligations that sit with you rather than with Bead
* [Two destinations, fixed by role](/split-funding#two-destinations-fixed-by-role), why you cannot name a payee
* [Consumer Fee Passthrough](/faqs-and-troubleshooting/payments-faqs/consumer-fee-passthrough-for-wallet-and-crypto-tenders), the legal and per-tender considerations
* Split funding and residuals are mutually exclusive on the same traffic

## Related

* [Split Funding](/split-funding)
* [Split Funding Reports](/split-funding/reports)
* [Create Payment](/payments/create-payment)
* [Update Merchant](/entity-management/merchant-management/update-merchant)


# Split Funding Reports

Reporting for partners using the Split Funding Program. These endpoints are partner-scoped only. There is no merchant or terminal scope, since the partner portion is withheld and distributed at the partner level.

Use Split Funding Reports when you need to:

* See the partner portion on individual payments.
* Reconcile a partner distribution by merchant and MID.
* See Bead's fee and partner net for a period.
* Confirm enrollment and split funding versus regular payment volume for a merchant.

For everything else about how the program works, the three amounts, the locked identity, the coverage check at payment creation, enrollment, and settlement behavior, see Split Funding Program.

> **These reports are the only place the amounts and Bead's fee are readable after payment creation.** Nothing is echoed back on the payment creation response, the payment status endpoints, or payment tracking.

### Split funding transactions

One row per split funding payment.

```http
GET /partners/{partnerId}/reports/split-funding-transactions
GET /partners/{partnerId}/reports/split-funding-transactions/csv
```

#### 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 payment status.                                          |
| `MerchantId`    | string  | Filter to a single merchant.                                       |
| `Page`          | integer | Page number.                                                       |
| `PageSize`      | integer | Results per page.                                                  |
| `SortBy`        | string  | Field to sort by. Not every response field is accepted, see below. |
| `SortDirection` | string  | Sort direction: `asc` or `desc`.                                   |

**Sorting.** `SortBy` accepts a narrower set than the response field list. `splitFundingFee`, `partnerNet`, `settlementId` and `merchantId` are not sortable on this report. `amount` is accepted as an alias for `total`, and `SortBy` matching is case insensitive.

#### Response fields

| Field             | Description                                                                                         |
| ----------------- | --------------------------------------------------------------------------------------------------- |
| `id`              | Payment identifier.                                                                                 |
| `activityDate`    | Date of the payment.                                                                                |
| `paymentCode`     | Payment reference code.                                                                             |
| `status`          | Payment status. Includes refund status for tenders where refunds are supported. See the note below. |
| `total`           | What the consumer paid.                                                                             |
| `partnerPortion`  | The amount withheld for the partner.                                                                |
| `merchantPortion` | Amount settled to the merchant.                                                                     |
| `splitFundingFee` | Bead's per-payment fee on this payment. Nullable, see below.                                        |
| `partnerNet`      | What remains of the partner portion after the fee. Nullable, see below.                             |
| `batchId`         | Batch the payment was grouped into.                                                                 |
| `settlementId`    | Settlement the payment belongs to.                                                                  |
| `merchantId`      | Merchant the payment belongs to.                                                                    |
| `merchantName`    | Merchant display name.                                                                              |

**`status` on this report is the payment's `PaymentStatus`, not a distribution status.** The two are different objects with different value sets, and the distribution statuses described in [Distribution status values](/#distribution-status-values) never appear in this column. Do not map one onto the other.

**`splitFundingFee` and `partnerNet` are nullable, and a null is meaningful.** `splitFundingFee` is null when no rate resolves for that payment's tender, which is the same condition that defers the payment at distribution assessment. `partnerNet` is null whenever `splitFundingFee` is null, because it cannot be derived without the fee. Treat a null in either column as a missing rate to chase with your Bead contact rather than as a fault in the report.

#### CSV export

The CSV variant additionally accepts `IncludeHeader`, `IncludeTotals`, `Columns`, and `Separator`. `Columns` is a CSV only parameter. The JSON endpoints always return the full row and cannot be narrowed.

**The CSV is not the JSON row.** It carries neither `id` nor `merchantId`, and it splits `activityDate` into separate `Date` and `Time` columns. Map the two shapes separately rather than assuming one parser covers both.

Downloaded files are named `bead-report_split-funding-transactions_...`, matching the route path.

**Refund handling (reserved):** no Bead tender currently supports a refund, so this does not come up in practice today. When a tender that supports refunds is added, refunded and partially refunded payments will be visible here and flagged by `status`. This report is where you will see which payments were excluded from a distribution, since that exclusion happens at the payment level, not at the settlement level. See the caution in [Residual Reporting](/settlement/residual-reporting).

### Split funding distribution summary

Reconciliation by merchant and MID, and the authoritative view of what a partner is owed and what was paid.

```http
GET /partners/{partnerId}/reports/split-funding-distribution-summary
GET /partners/{partnerId}/reports/split-funding-distribution-summary/period-summary
GET /partners/{partnerId}/reports/split-funding-distribution-summary/account-summary
GET /partners/{partnerId}/reports/split-funding-distribution-summary/csv
```

> **This is where the deposit fee becomes visible.** Bead's per-payment fee is checked when a payment is created, but the settlement fees are charged per settlement event and cannot be evaluated per payment. They are applied at distribution assessment and appear here as `depositFee`. Reconcile against this report rather than against per-payment arithmetic.

#### Base report

`GET /partners/{partnerId}/reports/split-funding-distribution-summary` returns per merchant and MID totals.

| Parameter       | Type    | Description                                                    |
| --------------- | ------- | -------------------------------------------------------------- |
| `From`          | date    | Start of the date range filter.                                |
| `To`            | date    | End of the date range filter.                                  |
| `MerchantId`    | string  | Filter to a single merchant.                                   |
| `Page`          | integer | Page number.                                                   |
| `PageSize`      | integer | Results per page.                                              |
| `SortBy`        | string  | Field to sort by. `merchantId` is not sortable on this report. |
| `SortDirection` | string  | Sort direction: `asc` or `desc`.                               |

**Sorting.** `merchantId` is not accepted by `SortBy` on this report. `amount` is accepted as an alias for `total`, and `SortBy` matching is case insensitive.

Selectable columns, on the base report and the CSV: `period`, `merchantId`, `merchantName`, `paymentCount`, `total`, `partnerPortion`, `merchantPortion`, `splitFundingFee`, `depositFee`, `partnerNet`.

The last three columns are the whole reconciliation, so the arithmetic is worth stating plainly:

```
partnerNet = partnerPortion - splitFundingFee - depositFee
```

`splitFundingFee` is Bead's per-payment fee for the period. `depositFee` is the settlement fee recovered from the partner portion. `partnerNet` is what is paid out.

**The response has two parts, and the distribution level fields are not on the rows.** Alongside the per merchant and MID rows, this report returns a separate top level `distributions[]` array. Each entry in that array carries `status` and `blockedReason` together with the period totals for the partner portion, Bead's fee and partner net. A merchant or MID row carries neither `status` nor `blockedReason`, so read them from `distributions[]`. See [Distribution status values](/#distribution-status-values) for what `status` means, for the `blockedReason` values, and for what to do when a distribution is held.

`distributions[]` is on this base report only. `status` and `blockedReason` do not appear on the period summary, do not appear on the account summary, and are not in the CSV, which writes the 10 selectable row columns only.

A settlement line can show a negative value where the recovered deposit fee exceeded that line's withheld amount. This is absorbed by the distribution total rather than blocking the line.

The CSV variant of this report accepts `Columns`, which the JSON endpoints do not. Downloaded files are named `bead-report_split-funding-distribution-summary_...`, matching the route path.

#### Period summary

`GET /partners/{partnerId}/reports/split-funding-distribution-summary/period-summary` returns partner portion amounts over time.

| Parameter  | Type   | Description                      |
| ---------- | ------ | -------------------------------- |
| `From`     | date   | Start of the date range filter.  |
| `To`       | date   | End of the date range filter.    |
| `Interval` | string | Bucket size for the time series. |

Each row carries the payment count, the partner portion, Bead's fee, and the effective ratio of partner portion to total for that period.

#### Account summary

`GET /partners/{partnerId}/reports/split-funding-distribution-summary/account-summary` returns enrollment and volume by merchant.

| Parameter | Type   | Description                                                                                                                                                                                                                                                                                                                |
| --------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `From`    | date   | Start of the date range filter.                                                                                                                                                                                                                                                                                            |
| `To`      | date   | End of the date range filter.                                                                                                                                                                                                                                                                                              |
| `Status`  | string | Filter by payment status, not by distribution status. It selects which payments go into the rollup, before the participation figures are calculated. Accepted values: `created`, `processing`, `completed`, `underpaid`, `overpaid`, `fullyRefunded`, `partiallyRefunded`, `expired`, `invalid`, `cancelled`, `abandoned`. |

Each row carries the enrolled merchant count, the split funding payment count, the regular payment count, and the split funding amount per account.

The split between split funding and regular payment counts also tells you which settlements were split-funding-only and which were mixed, which determines how the deposit settlement fee was handled for that merchant. See [Settlement behavior](/#settlement-behavior).

### What to reconcile, and when

| Question                                                      | Where to look                                                                                           |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| What was withheld on a given payment                          | Split funding transactions                                                                              |
| What Bead's per-payment fee came to                           | The `splitFundingFee` column, on either report                                                          |
| Why a distribution paid less than the sum of withheld amounts | Distribution summary, the `depositFee` column                                                           |
| Why a distribution was not paid at all                        | Distribution summary, the `distributions[]` array: `status`, and `blockedReason` for the specific check |
| Whether a merchant's day was split-funding-only or mixed      | Account summary, split funding count against regular count                                              |

**Check the first month's distribution summary against your own expectations, per tender.** The creation-time coverage check protects you from a partner portion that cannot cover its own per-payment fee, but it cannot see the settlement fees. A low-volume merchant carries a larger share of those, so the first distribution is where an undersized consumer fee shows up.

### Authorization

All endpoints on this page are callable with a partner API key for that partner's own `partnerId`, or for any partner beneath it in the hierarchy.

### Next steps

* Split Funding Program for how the program works and how to enroll a merchant
* [Settlements](/settlement/settlements) and [Residual Reporting](/settlement/residual-reporting) for settlement-level fee data, which is not the source of truth for a partner distribution
* [Payment history](/reporting) for standard reporting


# 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 is an API key sent in the `X-Api-Key` header. Payments always uses the terminal API key in `X-Api-Key`; it does not use OAuth or a username and password. See [Authentication](/authentication) for the current, API key first guidance and examples.
>
> This page documents the OAuth 2.0 password grant model used by Boarding and Entity Management. 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 identity at a glance

This describes the legacy OAuth model 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                                  |
| --------------------------------------- | --------------- | --------------------------------------- | -------------- | ------------------------------------------------ | ------------------------------------------------ |
| 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 the legacy OAuth model

* 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

#### 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

#### Payments does not use OAuth

Payments requests authenticate with the terminal API key in the `X-Api-Key` header. Do not request OAuth tokens for Payments, and do not look for a terminal username and password. See [Authentication](/authentication).

#### 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. Send each request as `POST` to the token endpoint with `Content-Type: application/x-www-form-urlencoded`. To target Production, use the `prod` realm instead of `nonprod`.

**Password grant fields**

| Field        | Value                      |
| ------------ | -------------------------- |
| `grant_type` | `password`                 |
| `client_id`  | Your provisioned client ID |
| `username`   | Your integrator username   |
| `password`   | Your integrator credential |
| `scope`      | `openid profile email`     |

**Refresh token grant fields**

| Field           | Value                      |
| --------------- | -------------------------- |
| `grant_type`    | `refresh_token`            |
| `client_id`     | Your provisioned client ID |
| `refresh_token` | The latest refresh token   |

**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`      |

New Payments integrations authenticate with `X-Api-Key: {apiKey}` instead of a bearer token. See [Authentication](/authentication).

#### Example Create a payment in Test / Non-prod

```http
POST https://api.test.devs.beadpay.io/Payments
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"
}
```

`POST /Payments/crypto` is deprecated but still supported and accepts the same request body. Use `POST /Payments` for new work.

The response includes a `paymentId` and a `paymentUrls` array with the hosted payment page URL to redirect the buyer to. It also includes `trackingId`, a legacy identifier kept for backward compatibility. Use `paymentId` for status checks.

#### Polling payment status

Retrieve the current payment status by `paymentId`

```
GET {API_BASE}/Payments/{paymentId}/tracking
```

This endpoint returns the current payment status payload.

`trackingId` is a legacy identifier. The older endpoint `GET {API_BASE}/Payments/tracking/{trackingId}` is deprecated but still supported for backward compatibility. New integrations should use `paymentId`.

### 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.

### Each ISO is its own instance

The ISO is the top of a hierarchy. Each ISO is its own instance, and there is no root above it that spans ISOs. Records under one ISO are separate from records under another, and one ISO cannot see or manage another ISO's records. Every tier described on this page, including Agents, Corporate Entities, and Master Merchants, sits under a single ISO.

### 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` (and the deprecated `POST /Payments/crypto`, which accepts the same body)
* `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</code> (also accepted on the deprecated <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`

Store `applicationId`. It is the identifier used by every onboarding endpoint. `envelopeId` is an internal signing reference and is not needed for any Bead API call.

#### 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 and paste. |
| [**Download OpenAPI**](/reference-guide/endpoint-index/download-openapi) | The link to the OpenAPI JSON file and how to request access. The file requires a Bead login.          |

### 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, sign in and download the OpenAPI file from [**Download OpenAPI**](/reference-guide/endpoint-index/download-openapi), then import it into Swagger, Insomnia, or Postman. If you do not have a Bead login, request one from Integration Support.

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`                       | Create a hosted payment page and receive `paymentId` and `paymentUrls`. The response also includes `trackingId`, a legacy identifier kept for backward compatibility            |
| GET    | `/Payments/{paymentId}/tracking`  | Get payment status by `paymentId`, including reclaim progress fields                                                                                                            |
| GET    | `/Payments/{paymentId}`           | Get a single payment by `paymentId`                                                                                                                                             |
| POST   | `/Payments/crypto`                | **Deprecated.** Same request body and response as `POST /Payments`. Still supported. Use `POST /Payments` for new integrations                                                  |
| GET    | `/Payments/tracking/{trackingId}` | **Deprecated.** Get payment status by `trackingId`. Still supported for backward compatibility. Use `GET /Payments/{paymentId}/tracking` for new integrations                   |
| GET    | `/Payments`                       | **Deprecated.** List payments for a terminal. Use `GET /Merchants/{id}/payments` or `GET /Partners/{id}/payments`, or `GET /Payments/{paymentId}/tracking` for a single payment |
| GET    | `/Payments/get-by-payment-code`   | **Deprecated.** Look up a payment by payment code. Use the `PaymentCode` filter on merchant or partner payment history                                                          |

**Onboarding (API key)**

These endpoints use API key authentication. For new integrations, start with the Draft endpoints. See [Draft and Submit (Recommended)](/onboarding/draft-and-submit).

| Method | Path                                                                           | Summary                                                                          |
| ------ | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| POST   | `/merchant-onboarding/applications/drafts`                                     | **Recommended.** Create a Draft application with whatever merchant data you have |
| PUT    | `/merchant-onboarding/applications/drafts/{applicationId}`                     | Update a Draft application. Replaces the stored merchant data                    |
| DELETE | `/merchant-onboarding/applications/drafts/{applicationId}`                     | Discard a Draft application. Permanently deletes the Draft and its attachments   |
| POST   | `/merchant-onboarding/applications`                                            | Initiate merchant onboarding with full merchant data in a single call            |
| POST   | `/merchant-onboarding/applications-short`                                      | Initiate merchant onboarding with minimal information in a single call           |
| 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 (legacy identifier)  |
| `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`                       | **Deprecated.** List merchants with pagination and filters. Still supported  |
| 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 schema

### What you can download

| Asset      | Purpose                                          | Format |
| ---------- | ------------------------------------------------ | ------ |
| OpenAPI v1 | Full REST specification for documented endpoints | JSON   |

#### Download

* **OpenAPI v1 (JSON):** <https://api.test.devs.beadpay.io/openapi/v1.json>

{% hint style="info" %}
**A Bead login is required.** The OpenAPI file is available to registered partners only. Opening the link prompts you to sign in. If you do not have a login, contact [Integration Support](/reference-guide/support-and-contacts/integration-support) and request OpenAPI access.
{% endhint %}

#### Request access

Email <support@bead.xyz> with the subject line `OpenAPI access request` and include:

* your company name
* the name and email address of each person who needs access
* the environment you are integrating against (Sandbox, Production, or both)

Integration Support sets up the login and replies with sign in details.

### 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

The login used to download the OpenAPI file is separate from your API credentials. It does not authenticate API requests.

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 or 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

Because the file requires a login, tools and coding agents usually cannot fetch the URL directly. Download the file while signed in, then import or attach the local copy.

### Versioning and updates

| Topic                  | Guidance                                                                                |
| ---------------------- | --------------------------------------------------------------------------------------- |
| Spec version           | OpenAPI v1 is the current 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                                                                                           |
| ------------------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------- |
| Prompted to sign in when opening the OpenAPI link | The file requires a Bead login              | Sign in with your Bead login. If you do not have one, request access from Integration Support |
| Tool or coding agent cannot fetch the OpenAPI URL | The URL requires a signed in session        | Download the file while signed in and import or attach the local copy                         |
| 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                                                                            |
| ------------------------------------------- | -------------------------------------------------------------------------------- |
| Request OpenAPI access                      | [Integration Support](/reference-guide/support-and-contacts/integration-support) |
| 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)
* [In-Person Payments (Physical Terminals)](/reference-guide/payment-flows/in-person-payments-physical-terminals)
* [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.

> This page covers which tenders to enable for a given business context. If you're deciding which tenders to render for a consumer at runtime based on what's currently active, see [Reading Tender Availability at Runtime](/reference-guide/payment-flows/reading-tender-availability-at-runtime).

#### 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.

For the broader in-person integration picture beyond tender selection, see [In-Person Payments (Physical Terminals)](/reference-guide/payment-flows/in-person-payments-physical-terminals).

**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.

This page addresses which tenders to make available for a given business context. It assumes you already know your terminal's currently active tender set. For how to read that set at runtime — rather than assuming a fixed list — and how to keep the UI correct as that set changes over time, see [Reading Tender Availability at Runtime](/reference-guide/payment-flows/reading-tender-availability-at-runtime).

#### 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)
* [Tender Types](/reference-guide/enumerations-and-schemas/tender-types)
* [Reading Tender Availability at Runtime](/reference-guide/payment-flows/reading-tender-availability-at-runtime)
* [In-Person Payments (Physical Terminals)](/reference-guide/payment-flows/in-person-payments-physical-terminals)
* [Managing Tender Types](/entity-management/terminal-management/managing-tender-types)


# Reading Tender Availability at Runtime

`tenderTypes` on a terminal is not static. It changes when a merchant is approved for an additional tender, and it changes when a channel's configuration is narrowed — for example, disabling BTC (on-chain) at a physical lane per Bead's standing recommendation for fast in-person checkout. An integration that hardcodes its tender list will work in testing and then quietly drift from what the terminal actually supports.

This page covers the runtime side of that problem: what a checkout or POS integration should read to decide which tender buttons to show a consumer right now, how often to read it, and what to do when a consumer picks a tender that turns out not to be available. It assumes the terminal is already configured correctly — for how tender enablement flows from onboarding through partner, merchant, location, and terminal configuration, see [Managing Tender Types](/entity-management/terminal-management/managing-tender-types).

#### The problem this solves

A partner that renders tender options from a fixed list in code — rather than from what the terminal reports — ships something that works at launch and breaks silently later:

* A merchant is approved for a new tender. The terminal now reports it as available, but the UI never offers it because the option isn't in the hardcoded list.
* BTC (on-chain) is disabled at a physical lane. The terminal stops reporting it, but the UI still shows the button. A consumer selects it, the payment fails, the merchant calls the partner, the partner calls Bead — and nothing in anyone's logs looks wrong, because the terminal was configured correctly the whole time.

Bead recommends that partners implement support for all tender types up front, even ones disabled by default, specifically so a tender can be turned on for a merchant without new development. That recommendation only holds if the integration reads enablement instead of hardcoding it. This page is the pattern that makes it true.

#### Read `tenderTypes` as a rendering input, not just a diagnostic

`GET /Terminals/{terminalId}` returns the terminal's current `tenderTypes` array. [Managing Tender Types](/entity-management/terminal-management/managing-tender-types) frames this endpoint as a verification and troubleshooting tool — check before creating payments, check when a tender isn't appearing. That framing is correct, but incomplete: for a checkout or POS surface, the same array is also the authoritative input for what to render.

Treat it as a lookup, not a one-time constant: when it's time to build the tender selection screen for a given terminal, use the terminal's current `tenderTypes` to decide which buttons appear. Don't decide that once at build time and bake it into the client.

#### When to read it

Read `tenderTypes` at the start of a session or terminal context — for example, when a POS lane initializes, when a checkout page loads for a given terminal, or when your backend first resolves which terminal a transaction will run against. Cache the result for that context.

A sensible refresh cadence is a cache TTL measured in hours, not minutes. There is currently no webhook or event that fires when a terminal's tender configuration changes — Bead's webhook events cover payment status transitions, not configuration changes — so there is no push signal to build against, and periodic re-read is the correct mechanism, not a workaround. If Bead introduces a configuration-change event or webhook in the future, prefer it over polling; this page will be updated to point to it.

Do not call `GET /Terminals/{terminalId}` on every transaction. Tender configuration changes rarely — on the order of onboarding events, not per-payment — and a call per transaction adds a round trip to every payment for data that is almost always unchanged. Partners will reasonably object to that cost, and it buys little: a cache measured in hours already keeps the UI current within a business-reasonable window.

#### Cache invalidation on failure

Re-read `tenderTypes` immediately after any tender-related payment failure — specifically, a failure indicating the selected tender isn't available for that terminal. This lets a stale cache self-correct on the next attempt rather than failing the same way repeatedly. A consumer who hits a stale "available" tender should see the corrected list on their very next try, not wait out the TTL.

#### The cache is for rendering, never for gating

This is the constraint that matters most: a cached tender list decides what buttons to show. It never decides whether a payment is allowed to proceed. The live payment response is always authoritative.

If a consumer selects a tender your cache says is available and the payment request comes back indicating otherwise, trust the live response and handle it as a graceful failure (below) — don't retry against the cache, and don't surface the discrepancy as if it were a client bug. A partner that trusts a cached list over an actual failure response has built something worse than a partner that never cached at all, because it will actively argue with the API instead of just being slow to update.

#### Graceful failure when a tender isn't available

When a consumer attempts a tender that the live payment response indicates isn't available for that terminal:

* Show a clear, plain-language message — not a raw API error code or message.
* Return the consumer to tender selection rather than leaving them on a dead-end screen.
* Trigger a cache refresh (see above) so the next render reflects current availability.

This is the same recovery shape used for an `underpaid` payment in [Under and Over Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling): the original payment attempt doesn't go through, the consumer is not left stranded, and the path forward is a fresh attempt, with the same tender or a different one, rather than trying to repair the one that failed. If you've already built that recovery flow for underpayment, you can reuse it here rather than building a second one. An `overpaid` payment is different: it is paid, so no retry is needed.

#### Design for change, not for a fixed set

A terminal's tender set reflects current approval and configuration, not a permanent contract. It changes when:

* a merchant is approved for an additional tender type, or
* a channel's tender configuration is narrowed (for example, disabling BTC (on-chain) at a fast in-person checkout lane, per the guidance in [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment)).

Design the integration around the terminal's tender set as something that can change, not something fixed at boarding time. Concretely, that means driving the tender selection UI from a periodically refreshed read of `tenderTypes` rather than from a value chosen once and hardcoded.

#### Is this required?

No. Bead recommends reading tender availability rather than hardcoding it, because it's the pattern that lets a merchant's tender changes take effect without a partner code change. It is not mandatory. A partner that hardcodes its tender list and instead invests in clean failure handling — a clear message, a return to tender selection, no raw error, no dead end — has built an acceptable integration. What matters is that a disabled or newly available tender never produces a confusing dead end for the consumer. Reading `tenderTypes` is the simplest way to get there; solid failure handling on a fixed list is the fallback for partners who choose not to.

#### Recommended pattern summary

1. Read `GET /Terminals/{terminalId}` at session or terminal-context start.
2. Cache `tenderTypes` for that context, with a TTL measured in hours.
3. Render tender options from the cache.
4. On any tender-related payment failure, treat the live response as authoritative, show a clear message, return the consumer to tender selection, and refresh the cache.
5. Never let a cached "available" override what a live payment response actually says.

#### Related pages

* [Managing Tender Types](/entity-management/terminal-management/managing-tender-types)
* [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment)
* [Under and Over Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling)
* [Get Terminal](/entity-management/terminal-management/get-terminal)
* [Payment Webhooks](/payments/payment-webhooks)


# In-Person Payments (Physical Terminals)

This page is for integrators building an in-person, customer-present checkout, where the payment is presented on a screen the consumer does not own: a dedicated consumer-facing display, or a cashier point-of-sale screen turned to face the consumer. It pulls together the guidance that matters most in that environment and links to the pages that cover each topic in detail.

#### The integration is the same

There is no separate in-person API. Payments on a physical terminal use the same endpoint, request fields, hosted payment page, webhooks, and status checks as any other Bead payment. If you have built an online checkout on Bead, you have already built most of an in-person one.

What changes the experience is the terminal type. A terminal created with a `type` of `physical` gives the consumer an in-person experience: each tender is presented as a QR code that the consumer scans and completes on their own phone. Getting the terminal type right is the most important configuration decision for an in-person integration.

Two things to know about terminal type:

* `type` is set when the terminal is created and cannot be changed later. A merchant that already accepts online payments on a `virtual` terminal needs a separate `physical` terminal for in-person acceptance. See [Terminal type: physical vs. virtual](/entity-management/terminal-management/terminal-lifecycle-concepts#terminal-type-physical-vs-virtual) and [Create Terminal](/entity-management/terminal-management/create-terminal).
* Physical terminals have lighter request requirements. `customer` and `refundEmail` are optional on physical terminals. See the terminal type considerations on [Create Payment](/payments/create-payment).

#### How the in-person experience works

* **Nothing to type on the shared screen.** The consumer is not asked to sign in or key in personal details on a device they do not own.
* **Every tender is a QR code.** For each tender enabled on the terminal, the hosted payment page presents a QR code. The consumer scans it and completes the payment in the wallet or payment app of their choice on their own phone.
* **Status can move straight to a final state.** Because the consumer completes the payment on their own device, Bead may not observe an in-progress step, so a payment can move from `created` directly to a final status without `processing`. See [Why is the processing status missing for some payments, especially on physical terminals?](/faqs-and-troubleshooting/payments-faqs/why-is-the-processing-status-missing-for-some-payments-especially-on-physical-terminals)

#### Things to think through for in-person payments

None of the items below change the integration. They are considerations to layer on top of it for an in-person environment. Bead does not prescribe how to build the client side of an in-person checkout; the goal here is to name the concepts to raise with your own engineers.

**Tender selection in your own interface**

Many in-person integrations show tender-specific options on their own screen, for example PayPal, Venmo, or crypto, and let the consumer choose before the payment is created. Pass the consumer's choice in `tenderTypes` so the hosted page opens directly on that tender.

To decide which options to show, read the terminal's current `tenderTypes` from `GET /Terminals/{terminalId}`. Some integrations show only the tenders that are currently available; others show every supported tender and disable the ones that are not currently active. Either approach works. See [Reading Tender Availability at Runtime](/reference-guide/payment-flows/reading-tender-availability-at-runtime) and the tender type selection guidance on [Create Payment](/payments/create-payment).

**Embedding the hosted page and knowing when it is done**

Load the first URL in `paymentUrls` in the web view or embedded browser your device app uses to show web content. The hosted payment page, including the QR code, renders inside it.

There are two common ways for your app to know the payment has finished:

* **Webhooks.** The standard approach, the same as any other environment, and the source of truth for the final status. See [Payment Webhooks](/payments/payment-webhooks).
* **A redirect your app watches for.** Set `redirectUrl` to an `https` URL you control, and have your app watch the embedded view for navigation to that URL. When it appears, close the view and show your own finalization screen.

A redirect is a signal for your user interface, not a payment confirmation. Always confirm the final status through a webhook or the tracking endpoint before completing the sale. See [Payment Statuses](/payments/payment-statuses).

**Email addresses are often unavailable**

In person, it is common to have no email address for the consumer. For most payments that does not matter. For crypto exception outcomes such as `underpaid` or `overpaid`, Bead needs an email address to send reclaim instructions. If none was provided, the hosted payment page prompts the consumer to enter one at that point.

If your point-of-sale system does have the consumer's email address, send it on the payment request, either as `refundEmail` or within a complete `customer` object. This avoids the prompt and gives the consumer a smoother experience. 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).

**Pass cart items when you have them**

Some tenders give a better consumer experience and better approval rates when line-item detail is provided. When your system has SKU-level cart data, send it in `cartItems` rather than sending only the total. See the cart items guidance on [Create Payment](/payments/create-payment).

**Consumer fees are yours to size and disclose**

Bead has no consumer fee capability. If your solution charges the consumer a fee, you calculate it, disclose it, and include it in the `requestedAmount` you send. Because fee cost and permissibility vary by tender, fees need to be sized per tender. Knowing the consumer's tender in your own interface before you create the payment is what makes that possible. See [Consumer Fee Passthrough for Wallet and Crypto Tenders](/faqs-and-troubleshooting/payments-faqs/consumer-fee-passthrough-for-wallet-and-crypto-tenders).

**Receipt requirements do not change**

In-person receipts follow the same rules as online receipts: show the payment method and final status, and show the exchange rate whenever a crypto-to-fiat conversion occurred. See [Receipts](/payments/receipts).

**BTC (on-chain) needs a deliberate decision**

BTC (on-chain) is disabled by default on physical terminals because on-chain confirmation is long-running. Decide first whether it fits the environment: it is a poor fit for a fast checkout line and can work for high-ticket or high-touch sales. If you enable it, a payment can stay in `processing` for several minutes or longer, so your solution needs a way to alert staff or the consumer when the payment reaches `completed`. Do not release goods until then. See [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment) and [Why do BTC (on-chain) payments take longer?](/faqs-and-troubleshooting/payments-faqs/why-do-btc-on-chain-payments-take-longer)

#### Related pages

* [Create Payment](/payments/create-payment)
* [Terminal Lifecycle (Concepts)](/entity-management/terminal-management/terminal-lifecycle-concepts)
* [Choosing Tender Types by Payment Environment](/reference-guide/payment-flows/choosing-tender-types-by-payment-environment)
* [Reading Tender Availability at Runtime](/reference-guide/payment-flows/reading-tender-availability-at-runtime)
* [Managing Tender Types](/entity-management/terminal-management/managing-tender-types)
* [Payment Statuses](/payments/payment-statuses)
* [Payment Webhooks](/payments/payment-webhooks)
* [Receipts](/payments/receipts)
* [Consumer Fee Passthrough for Wallet and Crypto Tenders](/faqs-and-troubleshooting/payments-faqs/consumer-fee-passthrough-for-wallet-and-crypto-tenders)


# Payment Scenarios

## Payment Scenarios

### Purpose

Payment Scenarios explains how to handle outcomes other than a straightforward `completed` payment.

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 overpayment, an underpayment, a reclaimable balance, or a 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 more or less crypto than requested and the payment ends as `overpaid` or `underpaid`.                   |
| [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto)                   | A payment ends as `overpaid`, `underpaid`, `expired`, `invalid`, or `cancelled`, and crypto must be returned to the payer. |
| [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status)                               | You need to see where a reclaim stands and when the return to the payer has completed.                                     |
| [Refunds for Crypto and Wallet Payments](/reference-guide/payment-scenarios/refunds-for-crypto-and-wallet-payments) | You need return-policy guidance for paid 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 `overpaid` or `underpaid`, open [Under and Over Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling).
3. If crypto is being returned to the payer, open [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto).
4. To follow a reclaim through to completion, open [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status).
5. If the payment was paid and the business wants to return funds afterward, open [Refunds for Crypto and Wallet Payments](/reference-guide/payment-scenarios/refunds-for-crypto-and-wallet-payments).
6. Apply the business handling in your UI, support workflow, and internal reconciliation process.

### Scenario routing guide

Every outcome below is a final status. A reclaim does not change it.

| Payment outcome                                                 | What it means for the business                                                                                                                           |
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `completed`                                                     | Paid. Follow your normal fulfillment workflow.                                                                                                           |
| `overpaid`                                                      | Paid. Fulfill the order. The requested amount settles to the merchant and the excess is returned to the payer through reclaim.                           |
| `underpaid`                                                     | Not paid. Treat as declined and do not fulfill. Offer the customer a new payment. The amount sent is returned to the payer through reclaim.              |
| `expired`                                                       | Not paid. Do not fulfill. If funds later arrive, they are handled through reclaim.                                                                       |
| `invalid`                                                       | Not paid. Do not fulfill. Any eligible funds are handled through reclaim.                                                                                |
| `cancelled`                                                     | Not paid. Do not fulfill. Any remaining funds may be reclaimed.                                                                                          |
| Reclaim progress on any of the above                            | `statusCode` does not change. Follow `refundLifecycleStatus`. See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status). |
| Customer wants money back after a paid crypto or wallet payment | Follow the refund or return guidance for paid orders, 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

In crypto wallet payment flows, the customer types the amount they send, so the amount received can differ from the amount requested. This page explains how to handle payments that end as `overpaid` or `underpaid`.

{% hint style="warning" %}
**These two outcomes are handled differently.** `overpaid` is a paid payment: fulfill the order. `underpaid` is a declined payment: do not fulfill, and offer the customer a new payment. Both are final statuses. Earlier versions of this guidance treated both as failed payments.
{% endhint %}

### When this page applies

Use this page when the payment outcome is `overpaid` or `underpaid`.

If the payment ends as `expired`, `invalid`, or `cancelled`, use the [reclaim guidance](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto) instead.

If the payment was paid and the customer later wants money back, use the [refund and return guidance](/reference-guide/payment-scenarios/refunds-for-crypto-and-wallet-payments) instead.

### What these outcomes mean

| Outcome     | What happened                                     | Paid? | What settles to the merchant | What goes back to the payer           |
| ----------- | ------------------------------------------------- | ----- | ---------------------------- | ------------------------------------- |
| `overpaid`  | The customer sent more than the requested amount. | Yes   | The requested amount         | The excess, through reclaim           |
| `underpaid` | The customer sent less than the requested amount. | No    | Nothing                      | The full amount sent, through reclaim |

A very small overpayment, within a per tender tolerance set by Bead, is classified as `completed` instead of `overpaid`. The payment settles normally and no reclaim is offered for the excess.

`overpaid` and `underpaid` are final statuses. `statusCode` does not change when the payer reclaims their funds. Reclaim progress is reported in `refundLifecycleStatus`, which reaches `completed` when the return is done. If the payer never reclaims, or the amount is below the minimum that can be returned, `refundLifecycleStatus` does not reach `completed`.

### How to handle an overpaid payment

When a payment ends as `overpaid`:

* Fulfill the order, the same as for `completed`
* Mark the order or invoice as paid
* Do not ask the customer to pay again
* Tell the customer they sent more than requested and that the excess will be returned through Bead's reclaim process
* Expect later webhooks as the return of the excess progresses, with `statusCode` still `overpaid`. Keep the order fulfilled

The requested amount is batched and settled to the merchant like a `completed` payment. The excess is held separately and returned to the payer. It is not part of the merchant's settlement.

### How to handle an underpaid payment

When a payment ends as `underpaid`:

* Do not fulfill the order
* Treat it like a declined payment. Nothing was processed for the order
* Do not ask the customer to send the difference to the same payment session. The session cannot be topped up
* Offer the customer a new payment, with the same tender or a different one
* Tell the customer the amount they sent will be returned through Bead's reclaim process
* Expect later webhooks as the return progresses, with `statusCode` still `underpaid`

### Reclaim versus refund

Keep these flows separate.

**Reclaim** is how Bead returns crypto to the payer that was not part of a paid amount. It applies to the excess on `overpaid`, and to the full amount received on `underpaid`, `expired`, `invalid`, and `cancelled`. Bead runs the reclaim flow. Your integration does not.

**Refund or return** guidance applies when the merchant later decides to return value to the customer for a paid order, under its normal return policy. Bead has no refund endpoint.

A completed reclaim is not a merchant initiated refund. When the excess on an `overpaid` payment is returned, the order has not been returned.

### Webhooks and status handling

Your integration should:

* Listen for payment status changes through webhooks
* Act on the first outcome for the payment: fulfill on `overpaid`, decline on `underpaid`
* Expect further webhooks as the reclaim progresses. Bead sends a payment webhook each time `refundLifecycleStatus` changes, with the payment's final `statusCode` unchanged
* Treat those later webhooks as record updates, not as a new fulfillment decision
* Avoid building merchant logic that assumes every customer will send the exact requested amount

See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status) for the reclaim fields and lifecycle values.

### Customer messaging guidance

Clear customer messaging reduces support friction.

**Suggested guidance for overpaid payments**

> Your payment was received and your order is confirmed. You sent more than the amount due, and the extra amount will be returned to you. Check your email for instructions to claim it.

**Suggested guidance for underpaid payments**

> Your payment did not go through because the amount received was lower than the amount due. The amount you sent will be returned to you. Check your email for instructions to claim it. To complete your purchase, please start a new payment.

### Fulfillment guidance

| Payment outcome                   | Fulfillment behavior                                            |
| --------------------------------- | --------------------------------------------------------------- |
| `completed`                       | Fulfill normally                                                |
| `overpaid`                        | Fulfill normally. The excess is returned to the payer           |
| `underpaid`                       | Do not fulfill. Offer a new payment                             |
| `expired`, `invalid`, `cancelled` | Do not fulfill. Follow the applicable reclaim or exception flow |

### Best practices

* Fulfill on `overpaid` exactly as you do on `completed`
* Treat `underpaid` as a decline and create a new payment for any retry
* Keep reclaim handling separate from merchant refund handling
* Make your webhook processing idempotent, and expect reclaim progress events after the outcome
* Store the requested amount, the amount received, the outcome, and the order decision for support and reconciliation

### Related pages

* [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto)
* [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status)
* [Payment Statuses](/payments/payment-statuses)
* [Payment Webhooks](/payments/payment-webhooks)


# Reclaiming Unconverted Crypto

## Reclaiming Unconverted Crypto

### Reclaiming Unconverted Crypto

Some payment outcomes leave crypto with Bead that is not part of a paid amount. In these cases, Bead returns that 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                                          |
| ----------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `overpaid`  | The payer sent more than the requested amount. The payment is paid and the requested amount settles to the merchant. | The excess only: the amount paid minus the amount requested. |
| `underpaid` | The payer sent less than the requested amount. The payment is not paid and nothing settles.                          | The full amount received.                                    |
| `expired`   | No valid payment was completed before the payment window expired.                                                    | Any funds received.                                          |
| `invalid`   | Irregular event such as multiple transactions, unsupported asset, compliance block, or extreme market movement.      | Any funds received.                                          |
| `cancelled` | The payment was cancelled before completion.                                                                         | Any funds received.                                          |

All of these are final statuses. A reclaim does not change `statusCode`. Its progress is reported in `refundLifecycleStatus`.

Reclaim returns crypto in the same asset and network that was received, subject to the constraints described below.

A very small overpayment, within a per tender tolerance set by Bead, is classified as `completed` instead of `overpaid`, and no reclaim is offered for it.

#### 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 leave funds with Bead that are not part of a paid amount.

Reclaim provides a consistent and compliant way to return that 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. If email is not yet available at that point, the hosted payment page collects it directly, see below.

#### 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.

Behavior differs by terminal type.

**Virtual terminals**

`refundEmail` is required on virtual terminal payment requests. This applies regardless of whether a `customer` object is also included, and a `customer.email` value does not substitute for it. A virtual terminal request that omits `refundEmail` returns `400 Bad Request`, even if a complete `customer` object with `email` is present.

**Physical terminals**

`refundEmail` is optional. If it is omitted but the request includes a `customer` object with `email`, Bead uses `customer.email` as the reclaim email automatically. If neither `refundEmail` nor `customer.email` is available, the hosted payment page prompts the payer to enter an email at the point reclaim is needed.

| Terminal type | `refundEmail` omitted, no `customer.email`                        | `refundEmail` omitted, `customer.email` present                       | `refundEmail` supplied                     |
| ------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------ |
| Virtual       | `400 Bad Request`: `refundEmail` is required                      | `400 Bad Request`: `refundEmail` is required regardless of `customer` | Reclaim instructions emailed automatically |
| Physical      | Hosted page prompts the payer for an email when reclaim is needed | Reclaim instructions emailed to `customer.email` automatically        | Reclaim instructions emailed automatically |

Send `refundEmail` as `null` rather than omitting it if your integration always includes the key but sometimes has no value yet to send on a physical terminal request. Do not send `refundEmail` as an empty string on any terminal type. An empty string is not treated the same as an absent or `null` value and can prevent Bead from having an email on file for reclaim.

#### 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 `overpaid`, `underpaid`, `expired`, `invalid`, or `cancelled`
2. Bead emails reclaim instructions when an email address is available, or prompts the payer for one on the hosted page first if it is not
3. Customer opens the reclaim link
4. Customer confirms a destination wallet address for the return
5. Bead returns the reclaimable crypto to the provided address
6. Bead updates the payment record, sends a payment webhook, and emails the payer a receipt for the return

For `overpaid`, the customer receives the excess they sent. For `underpaid`, the customer receives the full amount they sent. In both cases the network cost of the return transfer can reduce the amount that arrives. The receipt shows the claim amount, the claim cost, and the amount returned.

If the reclaimable amount is below the minimum that can be transferred on that network (for example, 1 USDC for USDC payments), the reclaim page shows the reclaim as unavailable and no return is sent. `refundLifecycleStatus` does not reach `completed`.

#### Tracking reclaim status

Reclaim progress is visible on the payment record. `refundLifecycleStatus` moves through `emailSent`, `customerClaimed`, and `processing` to `completed`, with `failed` and `rejected` for returns that could not be completed. `refundInitiatedAt` is set when the payer is first emailed reclaim instructions or starts a claim, and `refundCompletedAt` is set when Bead confirms the return. `statusCode` does not change.

Bead sends a payment webhook each time `refundLifecycleStatus` changes. You can also read the reclaim fields from `GET /Payments/{paymentId}/tracking`, `GET /Payments/{paymentId}`, or merchant and partner payment history. See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status) for the fields, lifecycle values, webhook handling, and example responses.

#### 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                                                          |
| --------------------------------- | ----------------------------------------------------------------------------- |
| `overpaid`                        | Paid. Fulfill the order. The excess is returned to the payer through reclaim. |
| `underpaid`                       | Not paid. Do not fulfill. Offer the customer a new payment.                   |
| `expired`, `invalid`, `cancelled` | Not paid. Do not fulfill.                                                     |

#### Suggested UI messaging

You can adapt the wording below for your checkout UI or support flows.

**Overpaid**

> Payment received and your order is confirmed. You sent more than the amount due. The extra amount will be returned to you through the reclaim process. Check your email for instructions.

**Underpaid**

> Payment not completed. The amount sent was less than the amount due. The crypto that was sent will be returned to you through the reclaim process. Please start a new payment to complete your purchase.

**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

On virtual terminals, `refundEmail` is required. Include it on every virtual terminal payment request, in addition to the required `customer` object.

On physical terminals, provide `refundEmail` up front if you can so the customer does not need to re-enter it later. If you omit it, a `customer.email` on the request is used automatically. If you send neither, the hosted payment page collects an email if reclaim is later needed.

If you send a `customer` object, ensure it is complete. Providing partial customer data may trigger required field validation.

#### Timing and reclaim windows

There is a single reclaim window: **30 days**, counted from the payment's last-updated timestamp, not its created timestamp. Any update to the payment record resets the 30-day clock from that point.

|                |                                                   |
| -------------- | ------------------------------------------------- |
| Reclaim window | 30 days from the payment's last-updated timestamp |

This is a single global setting, not a value that varies by merchant, partner, or terminal.

After the 30-day window, the reclaim link no longer works. If a payer has not completed reclaim within the window, they should contact support.

#### 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**

The network cost of the return transfer can reduce the amount the payer receives, depending on the asset and network. The payer's receipt shows the claim amount, the claim cost, and the amount returned.

**Minimum amount**

A reclaim below the minimum transfer amount for the network cannot be sent. For USDC, the minimum is 1 USDC.

**Customer support visibility**

For operational tracking, integrators should store and be ready to share:

* `paymentId`
* `statusCode`
* `refundLifecycleStatus` and `refundCompletedAt`
* 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. An underpaid payment cannot be topped up. If the payer still wants to pay, create a new payment and direct the payer to the new hosted payment page.

**Should the merchant refund the excess on an overpaid payment?**

No. Bead returns the excess to the payer through reclaim. The merchant is settled the requested amount only.

**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.

**How do I know when the return is complete?**

Listen for the payment webhook where `refundLifecycleStatus` is `completed`, or read the payment record. At that point `refundCompletedAt` is set. `statusCode` keeps its final value. See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status).

**If the payer did not provide an email, can reclaim still happen?**

On physical terminals, yes. If neither `refundEmail` nor `customer.email` was available on the original request, the hosted payment page prompts 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.

Virtual terminals require `refundEmail` on the original request, so this scenario does not apply there.

#### Related pages

* [Payment Statuses](/payments/payment-statuses)
* [Under and Over Payment Handling](/reference-guide/payment-scenarios/under-and-over-payment-handling)
* [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status)
* [Payment Webhooks](/payments/payment-webhooks)


# Tracking Reclaim Status

When a payment ends as `overpaid`, `underpaid`, `expired`, `invalid`, or `cancelled`, Bead returns crypto to the payer through the reclaim flow. For `overpaid`, only the excess is returned. This page explains how to see where a reclaim stands and how to tell when the return is complete.

### Summary

* Reclaim progress is on the payment, in `refundLifecycleStatus`, `refundInitiatedAt`, `refundCompletedAt`, and `refundFailureReason`.
* Bead sends a payment webhook each time `refundLifecycleStatus` changes.
* When Bead completes the return, `refundLifecycleStatus` becomes `completed` and `refundCompletedAt` is set. `statusCode` does not change. `overpaid`, `underpaid`, `expired`, `invalid`, and `cancelled` are final statuses.
* A completed reclaim does not change the fulfillment decision. An `overpaid` payment is a paid order before and after the excess is returned.
* The field name `refundLifecycleStatus` describes the reclaim return to the payer. It does not describe a merchant initiated refund. There is no refund endpoint. See [Refunds for Crypto and Wallet Payments](/reference-guide/payment-scenarios/refunds-for-crypto-and-wallet-payments).

### Where to read reclaim status

| Source                                 | Refund lifecycle fields | Notes                                                                                                                                                                                |
| -------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Payment webhooks                       | Included                | Sent each time `refundLifecycleStatus` changes, to the terminal webhook URL and any `webhookUrls` on the payment. See [Payment Webhooks](/payments/payment-webhooks).                |
| `reclaimStatusChanged` partner webhook | Included                | Optional partner level event for every merchant under the partner. See [Partner Webhooks](/onboarding/partner-webhooks).                                                             |
| `GET /Payments/{paymentId}/tracking`   | Returned                | Status payload for one payment, same shape as the payment webhook.                                                                                                                   |
| `GET /Payments/{paymentId}`            | Returned                | Full record for one payment.                                                                                                                                                         |
| `GET /Merchants/{id}/payments`         | Returned                | Payment history for a merchant. Filter with `StatusCode=overpaid` and `StatusCode=underpaid` (and `expired`, `invalid`, `cancelled` if relevant), then read `refundLifecycleStatus`. |
| `GET /Partners/{id}/payments`          | Returned                | Payment history across a partner's merchants. Same schema as merchant scope.                                                                                                         |
| `GET /Terminals/{id}/payments`         | Not returned            | Terminal scope uses a different schema without the refund fields. Use merchant scope, partner scope, or a single payment read.                                                       |

Authenticate single payment reads the same way you authenticate your other Payments reads, with your terminal API key in the `X-Api-Key` header. Reporting endpoints accept `X-Api-Key` as described on [Merchant Payments](/reporting/merchant-payments) and [Partner Payments](/reporting/partner-payments).

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

### Reclaim fields

| Field                   | Type              | Meaning                                                                                                                                                                                                              |
| ----------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `refundLifecycleStatus` | string or null    | Where the reclaim stands. See the lifecycle values below. A value of `null` means the same as `notStarted`.                                                                                                          |
| `refundInitiatedAt`     | date-time or null | The first time Bead emailed the payer reclaim instructions or the payer started a claim, whichever happened first. It is set once and is not changed afterward. It does not mean the return transaction has started. |
| `refundCompletedAt`     | date-time or null | When Bead confirmed the return to the payer. Null until the return is confirmed.                                                                                                                                     |
| `refundFailureReason`   | string or null    | Populated when a return attempt failed.                                                                                                                                                                              |

The response does not include a field for the amount returned. The payer receives a receipt by email when the return completes.

### Lifecycle values

| `refundLifecycleStatus` | What it means                                                                                                                     | Who acts next                                  |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `notStarted`            | No reclaim is in progress.                                                                                                        | Nothing is due.                                |
| `emailSent`             | Bead emailed the payer reclaim instructions.                                                                                      | The payer opens the link and starts the claim. |
| `customerClaimed`       | The payer started the claim and provided where to send the funds.                                                                 | Bead completes the return.                     |
| `processing`            | The return is being processed. On Bitcoin payments, a claim can stay in `processing` until the funds to return are ready to send. | Bead completes the return.                     |
| `completed`             | Bead confirmed the return to the payer. `refundCompletedAt` is set.                                                               | Nothing is due.                                |
| `failed`                | The return attempt failed. `refundFailureReason` is populated.                                                                    | Contact Bead support with the `paymentId`.     |
| `rejected`              | The return request was rejected during Bead's verification review.                                                                | Contact Bead support with the `paymentId`.     |

### What a completed return means

| `statusCode` (unchanged)          | What `refundLifecycleStatus` = `completed` means                             |
| --------------------------------- | ---------------------------------------------------------------------------- |
| `overpaid`                        | The excess went back to the payer. The payment is paid and the order stands. |
| `underpaid`                       | The full amount sent went back to the payer. The payment was not paid.       |
| `expired`, `invalid`, `cancelled` | The funds received went back to the payer. The payment was not paid.         |

Use `refundLifecycleStatus` equal to `completed` together with `refundCompletedAt` as your completion signal.

If no return happens, `refundLifecycleStatus` does not reach `completed`. This is the case when the payer never claims, or when the reclaimable amount is below the minimum that can be transferred on that network. See [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto).

### Example responses

The examples are abbreviated to the fields that matter for reclaim.

An overpaid payment where the payer has been emailed reclaim instructions for the excess:

```json
{
  "paymentId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
  "paymentCode": "PMT-10452",
  "statusCode": "overpaid",
  "amounts": {
    "requested": { "inPaymentCurrency": { "amount": 100 } },
    "paid": { "inPaymentCurrency": { "amount": 105 } }
  },
  "refundLifecycleStatus": "emailSent",
  "refundInitiatedAt": "2026-10-06T13:57:20.207Z",
  "refundCompletedAt": null,
  "refundFailureReason": null
}
```

The same payment after the excess is returned:

```json
{
  "paymentId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
  "paymentCode": "PMT-10452",
  "statusCode": "overpaid",
  "refundLifecycleStatus": "completed",
  "refundInitiatedAt": "2026-10-06T13:57:20.207Z",
  "refundCompletedAt": "2026-10-06T14:20:41.000Z",
  "refundFailureReason": null
}
```

An underpaid payment after the amount sent is returned:

```json
{
  "paymentId": "7a41d2f0b9e84c51a3f6e2d9c8b7a610",
  "paymentCode": "PMT-10488",
  "statusCode": "underpaid",
  "refundLifecycleStatus": "completed",
  "refundInitiatedAt": "2026-10-06T16:02:11.430Z",
  "refundCompletedAt": "2026-10-06T16:31:05.000Z",
  "refundFailureReason": null
}
```

### Using webhooks and reads together

* Listen for payment webhooks. Each change to `refundLifecycleStatus` arrives as a payment webhook with the full payload.
* Update your records from each event. Do not reverse a fulfillment decision because of a reclaim event.
* If you missed an event, read `GET /Payments/{paymentId}/tracking` for a single payment, or filter merchant or partner payment history by `StatusCode` (for example `overpaid` and `underpaid`) and read `refundLifecycleStatus`.
* If you poll instead of using webhooks: while `refundLifecycleStatus` is `emailSent`, the next step belongs to the payer, so check infrequently. While it is `customerClaimed` or `processing`, check more often, with backoff. Stop when it is `completed`, `failed`, or `rejected`.
* Bead does not publish a fixed return time. Completion depends on the payer starting the claim and on the return being confirmed.
* Reclaim must be started within 30 days of the payment's last updated timestamp. See [Reclaiming Unconverted Crypto](/reference-guide/payment-scenarios/reclaiming-unconverted-crypto).

### Suggested customer messaging

| `refundLifecycleStatus`           | Suggested message                                                                                         |
| --------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `emailSent`                       | We emailed instructions to return your funds. Open the link in that email and confirm where to send them. |
| `customerClaimed` or `processing` | Your return is being processed.                                                                           |
| `completed`                       | Your funds have been returned to the wallet address you provided. A receipt was emailed to you.           |
| `failed` or `rejected`            | We could not complete your return. Please contact support and give them your payment code.                |

### Frequently asked questions

**Can I get a webhook when the reclaim completes?**

Yes. Bead sends a payment webhook each time `refundLifecycleStatus` changes, including when it becomes `completed`. `statusCode` in that webhook is unchanged.

**Does a completed reclaim on an `overpaid` payment mean the order was returned?**

No. It means Bead returned the excess of an overpayment to the payer. The merchant was settled the requested amount and the order stands.

**Does a completed reclaim mean the merchant refunded the customer?**

No. It means Bead returned funds the payer sent that were not part of a paid amount. Merchant return policy for paid orders is separate. See [Refunds for Crypto and Wallet Payments](/reference-guide/payment-scenarios/refunds-for-crypto-and-wallet-payments).

**Why is `refundInitiatedAt` set when the payer has not done anything yet?**

It is set when Bead first emails the payer reclaim instructions, so it marks the start of the reclaim, not the start of the return transaction.

**Where do I find the amount that was returned?**

The payment record does not carry a returned amount. For an overpaid payment, the reclaimable amount is `amounts.paid` minus `amounts.requested`, in the payment currency. For the other statuses, it is `amounts.paid`. The network cost of the return transfer can reduce what arrives. The payer receives a receipt by email showing the amount returned.

### Related pages

* [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 Statuses](/payments/payment-statuses)
* [Payment Webhooks](/payments/payment-webhooks)
* [Merchant Payments](/reporting/merchant-payments)




---

[Next Page](https://developers.bead.xyz/llms-full.txt/1)

