> For the complete documentation index, see [llms.txt](https://developers.bead.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.bead.xyz/quick-start.md).

# 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.md) and [Test with Klarna](/testing/test-with-klarna-recommended.md).

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

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


---

# 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
If you need additional information that is not directly available in this page, you can query the documentation 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/quick-start.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.

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.
