# Create Payment

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

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

### Overview

Endpoint:

`POST /Payments/crypto`

Sandbox base URL:

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

Use this endpoint to:

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

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

### Step 1: Authenticate

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

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

Required headers:

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

Authentication guidance:

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

See Authentication for full token and legacy OAuth details.

### Step 2: Create a hosted payment URL

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

Endpoint:

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

Headers:

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

### Request shape guidance

At a minimum, payment creation requests should include:

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

Other common fields include:

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

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

### Terminal type considerations

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

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

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

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

#### Terminal type = virtual

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

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

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

#### Terminal type = physical

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

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

#### Customer object rule

Do not send a partial `customer` object.

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

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

Optional customer fields include:

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

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

### Cart items

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

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

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

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

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

### Tender type selection

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

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

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

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

**BTC Classic tender selection**

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

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

Only present BTC Classic when the merchant’s payment environment can support delayed confirmation. BTC Classic 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 Classic, make sure your integration can:

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

### Minimum payment amounts

Minimum payment amounts can vary by tender.

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

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

### Sandbox transaction amount limit

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

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

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

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

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

### Example request body: basic hosted payment

Use this as the default hosted payment example.

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

### Example request body: virtual terminal

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

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

### Example request body: physical terminal

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

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

### Example request body: with customer details

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

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

### Example request body: with cart items

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

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

### Example request body: tender type selection

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

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

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

### **Example request body: BTC Classic tender selection**

Use `bitcoin` when you want to limit the hosted payment experience to BTC Classic / 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 Classic payments can remain in `processing` for several minutes or longer. Do not fulfill the order based on `processing`. Wait until the payment reaches `completed`.

### Example request body: expanded

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

Notes:

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

### Example response

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

### Key fields

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

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

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

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

`tenderTypes` — Optional array used to limit or direct the payment to specific tender types. The selected tender types must already be enabled for the merchant, location, and terminal. Passing `tenderTypes` does not enroll the merchant into a new tender type. Use `bitcoin` for BTC Classic / 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 Classic, design the checkout flow for asynchronous completion: acknowledge `processing`, keep the order or invoice pending, and fulfill only after `completed`. When you limit a payment to USDC on Base or USDC on Solana, the `requestedAmount` must be at least `1.00`.

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

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

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

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

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

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

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

### Underpayments and overpayments

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

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

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

Recommended handling:

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

### Email behavior for reclaim

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

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

### Webhooks

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

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

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

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

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

### Step 3: Present the hosted page

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

Common patterns:

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

Redirect behavior:

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

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

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

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

### Step 4: Confirm payment status

You can confirm payment status using webhooks or polling.

#### Option A: Webhooks

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

#### Option B: Polling

Endpoint:

```http
GET https://api.test.devs.beadpay.io/payments/tracking/{trackingId}
```

Headers:

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

Example curl:

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

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

Typical statuses:

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

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

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

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

### Troubleshooting

#### 401 Unauthorized

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

#### Hosted page will not load

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

#### Validation error on payment creation

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

#### Invalid cart item values

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

#### Payment amount exceeds terminal maximum limit

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

#### No status change

* Confirm you are polling the correct `trackingId`.
* 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 Classic / Bitcoin on-chain and the status is `processing`, the payment may still be waiting for Bitcoin network confirmation. This can take several minutes or longer. Do not create a duplicate payment unless the original payment reaches a final unsuccessful state or the customer intentionally starts over.

### Suggested checkout error handling

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

Example message:

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

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

### Next steps

After you can create and complete a payment in Sandbox:

* Configure [Payment Webhooks](/payments/payment-webhooks.md) and verify your endpoint receives status events
* Use [Payment Statuses](/payments/payment-statuses.md) for ad hoc queries and troubleshooting
* Review [Why do BTC Classic payments take longer?](/faqs-and-troubleshooting/payments-faqs/why-do-btc-classic-payments-take-longer.md) 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.md) before enabling BTC Classic in physical, digital, invoice, or delayed-fulfillment flows
* Explore [Reporting](#troubleshooting) and [Settlement](/settlement.md) to build payment history and reconciliation jobs
* When ready for Production, request production credentials and switch API base URLs to Production


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developers.bead.xyz/payments/create-payment.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
