@bitpal/mcp 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -116
- package/dist/stdio.js +760 -278
- package/package.json +1 -1
package/dist/stdio.js
CHANGED
|
@@ -4067,28 +4067,28 @@ var docs_data_default = {
|
|
|
4067
4067
|
title: "API reference",
|
|
4068
4068
|
url: "https://bitpal.io/docs/#api",
|
|
4069
4069
|
markdownUrl: "https://bitpal.io/docs/md/api.md",
|
|
4070
|
-
markdown: '# API reference\n\nEvery public endpoint, generated from the OpenAPI 3.1.0 spec (version 1.0.0). Download it: [openapi.json](/docs/openapi.json).\n\n## Basics\n\n| Topic | Details |\n| --- | --- |\n| Base URL | `https://api.bitpal.io` \u2014 test and live share the host; the API key prefix selects the mode. |\n| Auth | `Authorization: Bearer bp_test_\u2026` (test) or `bp_live_\u2026` (live). Keep keys on your server. |\n| Format | JSON. Success: `{ "data": \u2026 }` (authenticated endpoints add `meta: { mode, requestId }`). Amounts are strings. |\n| Errors | `{ "error", "code"?, "message"?, "retryable"?, "trace_id"?, "details"? }` \u2014 see [Errors](/docs/md/errors.md). |\n| Idempotency | `Idempotency-Key` (8\u2013128 chars) is enforced on `POST /v1/checkout/sessions`; other endpoints note when it is not. |\n\n## Checkout sessions\n\nCreate a session per order, send the buyer to the hosted page (or build your own UI), and track it.\n\n### `POST /v1/checkout/sessions` \u2014 Create a checkout session\n\nCreates a session for one order and returns the hosted `checkout_url`. Line items take either `amount` (plain decimal string, e.g. "29") or `amount_atomic` (6-decimal integer string), or reference a `price_id`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreateCheckoutSessionRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `line_items` | array of (object \\| object) | Yes | Raw items (`name`, `currency`, `amount` XOR `amount_atomic`) or price items (`price_id`). Do not mix the two kinds. (\u2265 1 items) |\n| `currency` | string | No | Session currency. Defaults to the line items\u2019 currency. (\u2265 1 chars) |\n| `success_url` | string (uri) | No | Where the buyer returns after paying. `{CHECKOUT_SESSION_ID}` is replaced with the session id. |\n| `cancel_url` | string (uri) | No | Where the buyer goes when cancelling. |\n| `customer_email` | string (email) | No | Buyer email for the receipt. |\n| `metadata` | map | No | Your key/value data, echoed back on the session. |\n| `expires_in_minutes` | integer | No | Payment window in minutes (1\u201360). Default 15. (1\u201360, default `15`) |\n| `tolerance_percent` | string | No | Accepted underpayment tolerance, as a percent string. |\n| `pay_chain` | string | No | Fix the payment network (CAIP-2, e.g. `eip155:8453`). Mutually exclusive with `allowed_pay_chains`. |\n| `allowed_pay_chains` | array of string | No | Networks the buyer may choose from (CAIP-2). (1\u20138 items) |\n| `allowed_assets` | array of object | No | Allowed (chain, token) cells. Takes precedence over `allowed_pay_chains`. (1\u201316 items) |\n\n```\n{\n "line_items": [\n {\n "name": "Pro plan",\n "amount": "29",\n "currency": "USDC"\n }\n ],\n "pay_chain": "eip155:84532",\n "expires_in_minutes": 30,\n "success_url": "https://your.store/thanks?session={CHECKOUT_SESSION_ID}"\n}\n```\n\n**Response `201`** \u2014 Session created.\n\n```\n{\n "data": {\n "id": "cs_Q2FxKhomCEju_OqvKfrQsTOh",\n "checkout_url": "https://bitpal.io/pay/cs_Q2FxKhomCEju_OqvKfrQsTOh?token=<session_token>",\n "session_token": "<session_token>",\n "status": "created",\n "amount_total": "29000000",\n "currency": "USDC",\n "expires_at": "2026-11-06T07:58:28.776Z",\n "tax": {\n "tax_amount": "0",\n "total_with_tax": "29000000"\n }\n },\n "meta": {\n "mode": "TEST",\n "requestId": "ec091faf-\u2026"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `409` \xB7 `422` \xB7 `429` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions` \u2014 List checkout sessions\n\nNewest first, in the API key\u2019s mode (test/live).\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `status` | query | string | No | Filter by status, or `succeeded` for all successful states. |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Sessions.\n\n```\n{\n "data": [\n {\n "id": "string",\n "status": "string",\n "mode": "TEST",\n "amount_total": "1000000",\n "amount_received": "string",\n "currency": "string",\n "pay_chain": "string",\n "pay_asset": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "deposit_address": "string",\n "refund_address": "string",\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "tx_hash": "string",\n "sweep_tx_hash": "string",\n "success_url": "string",\n "cancel_url": "string",\n "metadata": {},\n "payment_source": "string",\n "payment_link_id": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "raw_status": "string"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}` \u2014 Retrieve a checkout session\n\nServer-side source of truth for an order, together with webhooks.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 The session.\n\n```\n{\n "data": {\n "id": "string",\n "status": "string",\n "mode": "TEST",\n "amount_total": "1000000",\n "amount_received": "string",\n "currency": "string",\n "pay_chain": "string",\n "pay_asset": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "deposit_address": "string",\n "refund_address": "string",\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "tx_hash": "string",\n "sweep_tx_hash": "string",\n "success_url": "string",\n "cancel_url": "string",\n "metadata": {},\n "payment_source": "string",\n "payment_link_id": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "line_items": [\n {\n "id": "string",\n "session_id": "string",\n "name": "string",\n "amount": "1000000",\n "currency": "string",\n "quantity": 0,\n "price_id": "string",\n "product_id": "string",\n "created_at": "2026-11-06T07:00:00.000Z"\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}/status` \u2014 Get session status (public)\n\nLightweight status for polling. No authentication.\n\n**Auth:** None \u2014 public endpoint\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Status.\n\n```\n{\n "data": {\n "status": "awaiting_deposit",\n "paid_at": null\n }\n}\n```\n\n**Errors:** `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}/public` \u2014 Get the public session view\n\nBuyer-facing view used by the checkout page: payment options, deposit address once issued, refunds. No authentication; CORS-open.\n\n**Auth:** None \u2014 public endpoint\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Public view.\n\n```\n{\n "data": {\n "id": "string",\n "status": "string",\n "token_decimals": 0,\n "pay_amount": "string",\n "mode": "TEST",\n "chain_id": 0,\n "token_address": "string",\n "payment_options": [\n {\n "caip2": "string",\n "chain_id": 0,\n "token_symbol": "string",\n "token_address": "string",\n "decimals": 0,\n "amount_atomic": "string"\n }\n ],\n "currency": "string",\n "amount_total": "1000000",\n "amount_received": "string",\n "chain": "string",\n "tx_hash": "string",\n "release_tx_hash": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "success_url": "string",\n "cancel_url": "string",\n "metadata": {},\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "tax_amount": null,\n "tax_rate": null,\n "pay_chain": "string",\n "pay_asset": "string",\n "deposit_address": "string",\n "payment_method": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "line_items": [\n {\n "name": "string",\n "amount": "1000000",\n "currency": "string",\n "quantity": 0\n }\n ],\n "payment_source": "string",\n "refunds": [\n {\n "kind": "string",\n "amount": "string",\n "token_symbol": "string",\n "decimals": 0,\n "tx_hash": "string",\n "caip2": "string"\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/checkout/sessions/{id}/deposit-address` \u2014 Issue the deposit address\n\nFixes the buyer\u2019s network, token and refund address, and returns the one-time deposit address with the exact amount. Calling again returns the same address and keeps the first refund address. Requires the session token (header `X-Session-Token` or body `session_token`).\n\n**Auth:** Session token \u2014 `X-Session-Token` header or `session_token` body field (no API key)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n| `X-Session-Token` | header | string | No | Session token from the create-session response. Alternative to the `session_token` body field. |\n\n**Request body** (`IssueDepositAddressRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `session_token` | string | No | From the create-session response. May be sent as the `X-Session-Token` header instead. |\n| `refundAddress` | string | Yes | Buyer refund address in the chosen network\u2019s format (EVM 0x\u2026, Tron T\u2026, Solana base58). Cannot change later. |\n| `chain` | string | No | CAIP-2 or short name of the chosen network. Required for multi-option sessions. (\u2265 1 chars) |\n| `token` | string | No | Token symbol of the chosen asset, e.g. USDC. Required for multi-option sessions. (\u2265 1 chars) |\n\n```\n{\n "session_token": "<session_token>",\n "refundAddress": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",\n "chain": "eip155:84532",\n "token": "USDC"\n}\n```\n\n**Response `200`** \u2014 Deposit address.\n\n```\n{\n "data": {\n "sessionId": "cs_\u2026",\n "chain": "eip155:84532",\n "depositAddress": "0x6b19e871c934aB52775B6712e110f3e4b0626347",\n "expectedToken": "0x943b94dacf1507f2658f1cfe103ac36ebc13ef65",\n "expectedAmount": "29000000",\n "feeAmount": "435000",\n "refundAddress": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",\n "deadline": "1793951938",\n "expiresAt": "2026-11-06T07:58:28.776Z"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \xB7 `409` \xB7 `410` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}/refunds` \u2014 List automatic refunds for a session\n\nOn-chain refunds BitPal made for this session (overpayment, expired underpayment, wrong token, late deposit).\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Refunds.\n\n```\n{\n "data": [\n {\n "id": "string",\n "kind": "string",\n "token": "string",\n "token_symbol": "string",\n "amount": "string",\n "amount_display": "string",\n "decimals": 0,\n "refund_address": "string",\n "tx_hash": "string",\n "caip2": "string",\n "block_number": "string",\n "created_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/fee-preview` \u2014 Preview the fee for an amount\n\nPass exactly one of `amount` or `amount_atomic`. The response is always atomic.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `amount` | query | string | No | Decimal string, e.g. "100". |\n| `amount_atomic` | query | string | No | 6-decimal integer string, e.g. "100000000". |\n\n**Response `200`** \u2014 Fee breakdown.\n\n```\n{\n "data": {\n "amount": "100000000",\n "fee_rate": "0.015",\n "fee_amount": "1500000",\n "net_amount": "98500000"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).\n\n## Products & prices\n\nCatalog objects. Prices are immutable.\n\n### `POST /v1/checkout/products` \u2014 Create a product\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n\n**Request body** (`CreateProductRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | Yes | 1\u2013200 chars |\n| `description` | string | No | \u2264 2000 chars |\n| `image_url` | string (uri) | No | |\n| `metadata` | map | No | |\n\n```\n{\n "name": "Pro plan"\n}\n```\n\n**Response `201`** \u2014 Product created.\n\n```\n{\n "data": {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/products` \u2014 List products\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `status` | query | "active" \\| "inactive" | No | |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Products.\n\n```\n{\n "data": [\n {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/products/{id}` \u2014 Retrieve a product\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n\n**Response `200`** \u2014 The product.\n\n```\n{\n "data": {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `PATCH /v1/checkout/products/{id}` \u2014 Update a product\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n\n**Request body** (`UpdateProductRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | No | 1\u2013200 chars |\n| `description` | string \\| null | No | \u2264 2000 chars |\n| `image_url` | string (uri) \\| null | No | |\n| `metadata` | map | No | |\n| `status` | "active" \\| "inactive" | No | |\n\n**Response `200`** \u2014 The updated product.\n\n```\n{\n "data": {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/checkout/products/{id}/prices` \u2014 Create a price\n\nPrices are immutable: to change an amount, create a new price. Pass exactly one of `amount` (decimal string) or `amount_atomic` (6-decimal integer string). The currency is derived from `token_symbol`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n\n**Request body** (`CreatePriceRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `amount` | string | No | Decimal string, e.g. "29". Mutually exclusive with `amount_atomic`. |\n| `amount_atomic` | string | No | 6-decimal integer string ("29000000" = 29). |\n| `token_symbol` | "USDC" \\| "USDT" \\| "JPYC" | No | The price\u2019s asset. The currency is derived from it. (default `"USDC"`) |\n| `billing_type` | "one_time" | No | Only `one_time` for now. (default `"one_time"`) |\n| `nickname` | string | No | Display name. (\u2264 200 chars) |\n| `lookup_key` | string | No | Your own stable key for this price. (1\u2013200 chars) |\n\n```\n{\n "amount": "29",\n "token_symbol": "USDC"\n}\n```\n\n**Response `201`** \u2014 Price created.\n\n```\n{\n "data": {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/products/{id}/prices` \u2014 List a product\u2019s prices\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Prices.\n\n```\n{\n "data": [\n {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/prices/{id}` \u2014 Retrieve a price\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Price id (`price_\u2026`). |\n\n**Response `200`** \u2014 The price.\n\n```\n{\n "data": {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `PATCH /v1/checkout/prices/{id}` \u2014 Update a price\u2019s nickname\n\nOnly `nickname` can change. Sending an amount/currency field returns `PRICE_IMMUTABLE_FIELD`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Price id (`price_\u2026`). |\n\n**Request body** (`UpdatePriceRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `nickname` | string \\| null | No | \u2264 200 chars |\n\n**Response `200`** \u2014 The updated price.\n\n```\n{\n "data": {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n## Payment links\n\nShareable hosted URLs for a price.\n\n### `POST /v1/checkout/payment-links` \u2014 Create a payment link\n\nA shareable hosted URL for one price. The link \u2194 price binding is fixed after creation.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n\n**Request body** (`CreatePaymentLinkRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `price_id` | string | Yes | The price this link sells (`price_\u2026`). |\n| `title` | string | No | 1\u2013200 chars |\n| `description` | string | No | \u2264 2000 chars |\n| `collect_email` | boolean | No | Ask the buyer for an email. Default true. (default `true`) |\n| `email_required` | boolean | No | Require the email. Default true; needs `collect_email`. (default `true`) |\n| `allowed_pay_chains` | array of string | No | Networks the buyer may choose from (CAIP-2 or short names). (1\u20138 items) |\n| `allowed_assets` | array of object | No | Allowed (chain, token) cells. Takes precedence over `allowed_pay_chains`. (1\u201316 items) |\n| `success_url` | string (uri) | No | |\n| `cancel_url` | string (uri) | No | |\n| `max_uses` | integer | No | Stop accepting payments after this many paid uses. (\u2265 1) |\n| `expires_at` | string (date-time) | No | ISO-8601 time after which the link stops working. |\n| `metadata` | map | No | |\n\n```\n{\n "price_id": "price_\u2026",\n "allowed_pay_chains": [\n "eip155:8453"\n ]\n}\n```\n\n**Response `201`** \u2014 Link created.\n\n```\n{\n "data": {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/payment-links` \u2014 List payment links\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `status` | query | "active" \\| "inactive" | No | |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Links.\n\n```\n{\n "data": [\n {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/payment-links/{id}` \u2014 Retrieve a payment link\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Payment link id (`plink_\u2026`). |\n\n**Response `200`** \u2014 The link, with product/price summary fields.\n\n```\n{\n "data": {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `PATCH /v1/checkout/payment-links/{id}` \u2014 Update a payment link\n\nOperational settings and active \u2194 inactive. The price cannot change \u2014 create a new link instead.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Payment link id (`plink_\u2026`). |\n\n**Request body** (`UpdatePaymentLinkRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `title` | string \\| null | No | 1\u2013200 chars |\n| `description` | string \\| null | No | \u2264 2000 chars |\n| `collect_email` | boolean | No | |\n| `email_required` | boolean | No | |\n| `allowed_pay_chains` | array of string \\| null | No | 1\u20138 items |\n| `allowed_assets` | array of object \\| null | No | 1\u201316 items |\n| `success_url` | string (uri) \\| null | No | |\n| `cancel_url` | string (uri) \\| null | No | |\n| `status` | "active" \\| "inactive" | No | |\n| `max_uses` | integer \\| null | No | \u2265 1 |\n| `expires_at` | string (date-time) \\| null | No | |\n| `metadata` | map | No | |\n\n**Response `200`** \u2014 The updated link.\n\n```\n{\n "data": {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \xB7 `409` \u2014 see [Errors](/docs/md/errors.md).\n\n## x402\n\nCharge per API call with HTTP 402 and EIP-3009 authorizations.\n\n### `GET /v1/x402/assets` \u2014 List x402 assets for this mode\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Response `200`** \u2014 Assets.\n\n```\n{\n "data": {\n "mode": "TEST",\n "assets": [\n {\n "caip2": "string",\n "network": "string",\n "chain_name": "string",\n "symbol": "string",\n "decimals": 0,\n "address": "string"\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/x402/requirements` \u2014 Build the 402 payment requirements\n\nReturns the body your resource server sends with `402 Payment Required`, including the fee split. Pass exactly one of `amount` or `amountAtomic`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n\n**Request body** (`X402RequirementsRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `network` | string | No | x402 network name (`base`, `base-sepolia`). Defaults to the mode\u2019s first asset. (\u2265 1 chars) |\n| `asset` | string | No | Token symbol. Default USDC. (\u2265 1 chars, default `"USDC"`) |\n| `payTo` | string | Yes | Your payout wallet (0x). |\n| `amount` | string | No | Gross amount per call as a decimal string, e.g. "0.1". The fee is taken from it. |\n| `amountAtomic` | string | No | Same as `amount`, as a 6-decimal integer string. |\n| `resource` | string | Yes | The URL being paid for. (1\u20132048 chars) |\n| `description` | string | No | \u2264 512 chars |\n| `mimeType` | string | No | \u2264 128 chars |\n| `maxTimeoutSeconds` | integer | No | How long the payer has to sign (max 3600). Default 300. (\u2264 3600, > 0) |\n\n```\n{\n "payTo": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",\n "amount": "0.1",\n "resource": "https://api.example.com/report"\n}\n```\n\n**Response `200`** \u2014 402 body.\n\n```\n{\n "data": {\n "x402Version": 1,\n "accepts": [\n {\n "scheme": "exact",\n "network": "string",\n "asset": "string",\n "payTo": "string",\n "maxAmountRequired": "1000000",\n "resource": "string",\n "description": "string",\n "mimeType": "string",\n "maxTimeoutSeconds": 0,\n "extra": {\n "name": "string",\n "version": "string",\n "chainId": 0\n },\n "feeBreakdown": {\n "netAmount": "1000000",\n "feeAmount": "1000000",\n "feeBps": 0,\n "feeRecipient": "string"\n }\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/x402/verify` \u2014 Verify an X-PAYMENT header\n\nChecks the EIP-3009 authorization against the requirements. Sends nothing on-chain.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n\n**Request body** (`X402PaymentRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `x402Version` | 1 | Yes | |\n| `paymentPayload` | object | Yes | |\n| `paymentRequirements` | object | Yes | |\n\n**Response `200`** \u2014 Valid.\n\n```\n{\n "data": {\n "valid": true,\n "payment_id": "string",\n "status": "string",\n "idempotent": true\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `409` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/x402/settle` \u2014 Settle an x402 payment\n\nBroadcasts the transfer and waits for the receipt. Serve the resource only when `status` is `confirmed`. Settlement is idempotent per authorization \u2014 retry with the same body on 5xx.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n\n**Request body** (`X402PaymentRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `x402Version` | 1 | Yes | |\n| `paymentPayload` | object | Yes | |\n| `paymentRequirements` | object | Yes | |\n\n**Response `200`** \u2014 Settlement result.\n\n```\n{\n "data": {\n "success": true,\n "payment_id": "string",\n "status": "string",\n "tx_hash": "string",\n "fee_tx_hash": "string",\n "network": "string",\n "idempotent": true\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `409` \xB7 `503` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/x402/payments/{id}` \u2014 Retrieve an x402 payment\n\nRecovery path when a settle response was lost \u2014 check here before settling again.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Payment id. |\n\n**Response `200`** \u2014 The payment.\n\n```\n{\n "data": {\n "id": "string",\n "status": "pending",\n "raw_status": "string",\n "resource": "string",\n "chain": "string",\n "asset": "string",\n "decimals": 0,\n "amount": "1000000",\n "gross_amount": "1000000",\n "payer": "string",\n "pay_to": "string",\n "tx_hash": "string",\n "created_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/x402/payments` \u2014 List x402 payments\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `limit` | query | integer | No | \u2264 100, > 0, default `20` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n| `status` | query | string | No | pending \\| confirmed \\| failed \\| needs_review |\n\n**Response `200`** \u2014 Payments.\n\n```\n{\n "data": [\n {\n "id": "string",\n "status": "pending",\n "raw_status": "string",\n "resource": "string",\n "chain": "string",\n "asset": "string",\n "decimals": 0,\n "amount": "1000000",\n "gross_amount": "1000000",\n "payer": "string",\n "pay_to": "string",\n "tx_hash": "string",\n "created_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).\n\n## Webhook endpoints\n\nManage where BitPal delivers signed events.\n\n### `POST /v1/webhooks` \u2014 Create a webhook endpoint\n\nThe response carries the `whsec_\u2026` signing secret \u2014 it is shown only once.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n\n**Request body** (`CreateWebhookEndpointRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `endpoint_url` | string (uri) | Yes | HTTPS URL that receives events. Private hosts are rejected. |\n| `subscribed_events` | array of string | Yes | Event names to deliver (see the `webhooks` section). (\u2265 1 items) |\n| `retry_policy` | object | No | Retry settings. Default 5 retries, exponential backoff. |\n| `dead_letter_destination` | string (uri) \\| null | No | Optional URL for dead-lettered deliveries. |\n\n```\n{\n "endpoint_url": "https://your.store/webhooks/bitpal",\n "subscribed_events": [\n "checkout.session.paid"\n ]\n}\n```\n\n**Response `201`** \u2014 Endpoint created.\n\n```\n{\n "data": {\n "id": "00000000-0000-4000-8000-000000000000",\n "mode": "TEST",\n "endpoint_url": "string",\n "subscribed_events": [\n "string"\n ],\n "retry_policy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "dead_letter_destination": "string",\n "status": "active",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z",\n "secret": "string"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/webhooks` \u2014 List webhook endpoints\n\nEndpoints of the API key\u2019s environment and mode. Note: list items use camelCase keys.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `limit` | query | integer | No | \u2264 200, default `50` |\n| `offset` | query | integer | No | default `0` |\n\n**Response `200`** \u2014 Endpoints.\n\n```\n{\n "data": [\n {\n "id": "00000000-0000-4000-8000-000000000000",\n "tenantId": "string",\n "environmentId": "string",\n "endpointUrl": "string",\n "subscribedEvents": [\n "string"\n ],\n "retryPolicy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "deadLetterDestination": "string",\n "status": "active",\n "createdAt": "2026-11-06T07:00:00.000Z",\n "updatedAt": "2026-11-06T07:00:00.000Z"\n }\n ],\n "pagination": {\n "limit": 0,\n "offset": 0\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string"\n }\n}\n```\n\n**Errors:** `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `PATCH /v1/webhooks/{id}` \u2014 Update a webhook endpoint\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Webhook endpoint id (uuid). |\n\n**Request body** (`UpdateWebhookEndpointRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `endpoint_url` | string (uri) | No | |\n| `subscribed_events` | array of string | No | |\n| `status` | "active" \\| "paused" \\| "disabled" | No | |\n| `retry_policy` | object | No | |\n| `dead_letter_destination` | string (uri) \\| null | No | |\n\n**Response `200`** \u2014 The updated endpoint.\n\n```\n{\n "data": {\n "id": "00000000-0000-4000-8000-000000000000",\n "mode": "TEST",\n "endpoint_url": "string",\n "subscribed_events": [\n "string"\n ],\n "retry_policy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "dead_letter_destination": "string",\n "status": "active",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/webhooks/{id}/rotate-secret` \u2014 Rotate the signing secret\n\nTakes effect immediately. The new secret is returned once \u2014 update your verifier.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Webhook endpoint id (uuid). |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n\n**Response `200`** \u2014 Endpoint with the new secret.\n\n```\n{\n "data": {\n "id": "00000000-0000-4000-8000-000000000000",\n "mode": "TEST",\n "endpoint_url": "string",\n "subscribed_events": [\n "string"\n ],\n "retry_policy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "dead_letter_destination": "string",\n "status": "active",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z",\n "secret": "string"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/developers/webhooks/test` \u2014 Send a test event\n\nPosts one signed sample event to an endpoint (no retries). The envelope id is prefixed `test_`, `data.test` is true and the request carries `X-Webhook-Test: true`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n\n**Request body** (`SendTestWebhookRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `webhook_id` | string (uuid) | Yes | Endpoint id. |\n| `event_type` | string | Yes | Event name to put in the envelope, e.g. checkout.session.paid. (1\u2013200 chars) |\n| `payload` | map | No | Custom `data` object. Defaults to a sample message. |\n\n```\n{\n "webhook_id": "00000000-0000-4000-8000-000000000000",\n "event_type": "checkout.session.paid"\n}\n```\n\n**Response `200`** \u2014 Delivery result.\n\n```\n{\n "data": {\n "delivery_id": "00000000-0000-4000-8000-000000000000",\n "webhook_id": "00000000-0000-4000-8000-000000000000",\n "endpoint_url": "string",\n "event_type": "string",\n "http_status": 0,\n "response_body": "string",\n "error": "string",\n "duration_ms": 0,\n "test": true,\n "sent_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \xB7 `409` \u2014 see [Errors](/docs/md/errors.md).\n\n## Webhook events\n\nEnvelope: `{ id, event, created_at, data }`, signed with `X-Webhook-Signature-256`. Verification and retries: [Webhooks](/docs/md/webhooks.md).\n\n| Event | Fires when | `data` fields |\n| --- | --- | --- |\n| `checkout.session.created` | A session is created. | `session_id`, `amount_total`, `currency`, `late` |\n| `checkout.session.deposit_detected` | Funds seen on-chain, awaiting confirmations. | `session_id`, `amount_total`, `currency`, `late`, `tx_hash`, `amount_received`, `received_token` |\n| `checkout.session.paid` | Confirmed \u2014 safe to fulfil. `late: true` if it arrived after the deadline. | `session_id`, `amount_total`, `currency`, `late`, `tx_hash` |\n| `checkout.session.settled` | Funds swept to your payout wallet. | `session_id`, `amount_total`, `currency`, `late`, `sweep_tx_hash` |\n| `checkout.session.unresolved` | Underpaid, wrong token, or a confirmed deposit reverted by a reorg (`reason`). | `session_id`, `amount_total`, `currency`, `late`, `reason`, `amount_expected`, `amount_received`, `received_token`, `tx_hash` |\n| `checkout.session.expired` | Deadline passed without full payment. | `session_id`, `amount_total`, `currency`, `late`, `reason`, `refund_tx_hash` |\n| `checkout.refund.recorded` | A refund was made on-chain (`kind`). | `session_id`, `kind`, `amount`, `token`, `tx_hash`, `refund_address` |\n| `x402.payment.resolved` | An x402 call that settled with `503` reached its outcome. Only fires for those \u2014 the normal path answers inline. | `payment_id`, `status`, `resource`, `amount`, `asset`, `chain`, `tx_hash`, `payer`, `resolved_by` |'
|
|
4070
|
+
markdown: '# API reference\n\nEvery public endpoint, generated from the OpenAPI 3.1.0 spec (version 1.0.0). Download it: [openapi.json](/docs/openapi.json).\n\n## Basics\n\n| Topic | Details |\n| --- | --- |\n| Base URL | `https://api.bitpal.io` \u2014 test and live share the host; the API key prefix selects the mode. |\n| Auth | `Authorization: Bearer bp_test_\u2026` (test) or `bp_live_\u2026` (live). Keep keys on your server. |\n| Format | JSON. Success: `{ "data": \u2026 }` (authenticated endpoints add `meta: { mode, requestId }`). Amounts are strings. |\n| Errors | `{ "error", "code", "retryable", "trace_id", "details"? }` \u2014 `error` is the human-readable message; branch on `code`. See [Errors](/docs/md/errors.md). |\n| Idempotency | `Idempotency-Key` (8\u2013128 chars) is honoured on every authenticated merchant `POST`; endpoints that do not enforce it say so. |\n\n## Checkout sessions\n\nCreate a session per order, send the buyer to the hosted page (or build your own UI), and track it.\n\n### `POST /v1/checkout/sessions` \u2014 Create a checkout session\n\nCreates a session for one order and returns the hosted `checkout_url`. Line items take either `amount` (plain decimal string, e.g. "29") or `amount_atomic` (6-decimal integer string), or reference a `price_id`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreateCheckoutSessionRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `line_items` | array of (object \\| any \\| any) | Yes | Raw items (`name`, `currency`, `amount` XOR `amount_atomic`) or price items (`price_id`). Do not mix the two kinds. (\u2265 1 items) |\n| `currency` | string | No | Session currency. Defaults to the line items\u2019 currency. (\u2265 1 chars) |\n| `success_url` | string (uri) | No | Where the buyer returns after paying. `{CHECKOUT_SESSION_ID}` is replaced with the session id. (\u2264 2048 chars) |\n| `cancel_url` | string (uri) | No | Where the buyer goes when cancelling. (\u2264 2048 chars) |\n| `customer_email` | string (email) | No | Buyer email for the receipt. |\n| `metadata` | map | No | Your key/value data, echoed back on the session. |\n| `expires_in_minutes` | integer | No | Payment window in minutes (1\u201360). Default 15. (1\u201360, default `15`) |\n| `tolerance_percent` | string | No | Deprecated. Underpayments are never accepted \u2014 a short payment becomes `unresolved_underpaid` and can be topped up to the same address. Only `"0"` is accepted; any other value returns 400. |\n| `pay_chain` | string | No | Fix the payment network: CAIP-2 (`eip155:84532`) or a short name (`base`), which resolves to the testnet in test mode and the mainnet in live mode. Mutually exclusive with `allowed_pay_chains`. |\n| `allowed_pay_chains` | array of string | No | Networks the buyer may choose from (CAIP-2 or short names, resolved like `pay_chain`). (1\u20138 items) |\n| `allowed_assets` | array of object | No | Allowed (chain, token) cells. Takes precedence over `allowed_pay_chains`. (1\u201316 items) |\n\n```\n{\n "line_items": [\n {\n "name": "Pro plan",\n "amount": "29",\n "currency": "USDC"\n }\n ],\n "pay_chain": "eip155:84532",\n "expires_in_minutes": 30,\n "success_url": "https://your.store/thanks?session={CHECKOUT_SESSION_ID}"\n}\n```\n\n**Response `201`** \u2014 Session created.\n\n```\n{\n "data": {\n "id": "cs_Q2FxKhomCEju_OqvKfrQsTOh",\n "checkout_url": "https://bitpal.io/pay/cs_Q2FxKhomCEju_OqvKfrQsTOh?token=<session_token>",\n "session_token": "<session_token>",\n "status": "created",\n "amount_total": "29000000",\n "currency": "USDC",\n "expires_at": "2026-11-06T07:58:28.776Z",\n "tax": {\n "tax_amount": "0",\n "total_with_tax": "29000000"\n }\n },\n "meta": {\n "mode": "TEST",\n "requestId": "ec091faf-\u2026"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `409` \xB7 `422` \xB7 `429` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions` \u2014 List checkout sessions\n\nNewest first, in the API key\u2019s mode (test/live).\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `status` | query | string | No | Filter by status, or `succeeded` for all successful states. |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Sessions.\n\n```\n{\n "data": [\n {\n "id": "string",\n "status": "string",\n "mode": "TEST",\n "amount_total": "1000000",\n "amount_received": "string",\n "currency": "string",\n "pay_chain": "string",\n "pay_asset": "string",\n "token_decimals": 0,\n "payment_method": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "deposit_address": "string",\n "refund_address": "string",\n "received_token": "string",\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "fee_amount_token": "string",\n "net_amount_token": "string",\n "tx_hash": "string",\n "deposit_tx_hash": "string",\n "sweep_tx_hash": "string",\n "success_url": "string",\n "cancel_url": "string",\n "metadata": {},\n "payment_source": "string",\n "payment_link_id": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "raw_status": "string"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}` \u2014 Retrieve a checkout session\n\nServer-side source of truth for an order, together with webhooks.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 The session.\n\n```\n{\n "data": {\n "id": "string",\n "status": "string",\n "mode": "TEST",\n "amount_total": "1000000",\n "amount_received": "string",\n "currency": "string",\n "pay_chain": "string",\n "pay_asset": "string",\n "token_decimals": 0,\n "payment_method": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "deposit_address": "string",\n "refund_address": "string",\n "received_token": "string",\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "fee_amount_token": "string",\n "net_amount_token": "string",\n "tx_hash": "string",\n "deposit_tx_hash": "string",\n "sweep_tx_hash": "string",\n "success_url": "string",\n "cancel_url": "string",\n "metadata": {},\n "payment_source": "string",\n "payment_link_id": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "line_items": [\n {\n "id": "string",\n "name": "string",\n "amount": "1000000",\n "currency": "string",\n "quantity": 0,\n "price_id": "string",\n "product_id": "string",\n "created_at": "2026-11-06T07:00:00.000Z"\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}/status` \u2014 Get session status\n\nLightweight status for polling from your server. Requires your API key (N13 \u2014 it is not a public endpoint). Buyer-facing pages should use `GET /v1/checkout/sessions/{id}/public`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Status.\n\n```\n{\n "data": {\n "status": "awaiting_deposit",\n "paid_at": null\n }\n}\n```\n\n**Errors:** `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}/public` \u2014 Get the public session view\n\nBuyer-facing view used by the checkout page: payment options, deposit address once issued, refunds. No authentication; CORS-open.\n\n**Auth:** None \u2014 public endpoint\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Public view.\n\n```\n{\n "data": {\n "id": "string",\n "status": "string",\n "token_decimals": 0,\n "pay_amount": "string",\n "mode": "TEST",\n "chain_id": 0,\n "token_address": "string",\n "payment_options": [\n {\n "caip2": "string",\n "chain_id": 0,\n "token_symbol": "string",\n "token_address": "string",\n "decimals": 0,\n "amount_atomic": "string"\n }\n ],\n "currency": "string",\n "amount_total": "1000000",\n "amount_received": "string",\n "chain": "string",\n "tx_hash": "string",\n "release_tx_hash": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "success_url": "string",\n "cancel_url": "string",\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "tax_amount": null,\n "tax_rate": null,\n "pay_chain": "string",\n "pay_asset": "string",\n "deposit_address": "string",\n "payment_deadline_at": "2026-11-06T07:00:00.000Z",\n "payment_method": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "line_items": [\n {\n "name": "string",\n "amount": "1000000",\n "currency": "string",\n "quantity": 0\n }\n ],\n "payment_source": "string",\n "refunds": [\n {\n "kind": "string",\n "amount": "string",\n "token_symbol": "string",\n "decimals": 0,\n "tx_hash": "string",\n "caip2": "string"\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/checkout/sessions/{id}/deposit-address` \u2014 Issue the deposit address\n\nFixes the buyer\u2019s network, token and refund address, and returns the one-time deposit address with the exact amount. Calling again returns the same address and keeps the first refund address. Requires the session token (header `X-Session-Token` or body `session_token`).\n\n**Auth:** Session token \u2014 `X-Session-Token` header or `session_token` body field (no API key)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n| `X-Session-Token` | header | string | No | Session token from the create-session response. Alternative to the `session_token` body field. |\n\n**Request body** (`IssueDepositAddressRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `session_token` | string | No | From the create-session response. May be sent as the `X-Session-Token` header instead. |\n| `refundAddress` | string | Yes | Buyer refund address in the chosen network\u2019s format (EVM 0x\u2026, Tron T\u2026, Solana base58). Cannot change later. |\n| `chain` | string | No | CAIP-2 or short name of the chosen network. Required for multi-option sessions. (\u2265 1 chars) |\n| `token` | string | No | Token symbol of the chosen asset, e.g. USDC. Required for multi-option sessions. (\u2265 1 chars) |\n\n```\n{\n "session_token": "<session_token>",\n "refundAddress": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",\n "chain": "eip155:84532",\n "token": "USDC"\n}\n```\n\n**Response `200`** \u2014 Deposit address.\n\n```\n{\n "data": {\n "sessionId": "cs_\u2026",\n "chain": "eip155:84532",\n "depositAddress": "0x6b19e871c934aB52775B6712e110f3e4b0626347",\n "expectedToken": "0x943b94dacf1507f2658f1cfe103ac36ebc13ef65",\n "expectedAmount": "29000000",\n "feeAmount": "435000",\n "refundAddress": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",\n "deadline": "1793951938",\n "expiresAt": "2026-11-06T07:58:28.776Z"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \xB7 `409` \xB7 `410` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}/refunds` \u2014 List automatic refunds for a session\n\nOn-chain refunds BitPal made for this session (overpayment, expired underpayment, wrong token, late deposit).\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Refunds.\n\n```\n{\n "data": [\n {\n "id": "string",\n "kind": "string",\n "token": "string",\n "token_symbol": "string",\n "amount": "string",\n "amount_display": "string",\n "decimals": 0,\n "refund_address": "string",\n "tx_hash": "string",\n "caip2": "string",\n "block_number": "string",\n "created_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/fee-preview` \u2014 Preview the fee for an amount\n\nPass exactly one of `amount` or `amount_atomic`. The response is always atomic.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `amount` | query | string | No | Decimal string, e.g. "100". |\n| `amount_atomic` | query | string | No | 6-decimal integer string, e.g. "100000000". |\n\n**Response `200`** \u2014 Fee breakdown.\n\n```\n{\n "data": {\n "amount": "100000000",\n "fee_rate": "0.015",\n "fee_amount": "1500000",\n "net_amount": "98500000"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).\n\n## Products & prices\n\nCatalog objects. Prices are immutable.\n\n### `POST /v1/checkout/products` \u2014 Create a product\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreateProductRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | Yes | 1\u2013200 chars |\n| `description` | string | No | \u2264 2000 chars |\n| `image_url` | string (uri) | No | \u2264 2048 chars |\n| `metadata` | map | No | |\n\n```\n{\n "name": "Pro plan"\n}\n```\n\n**Response `201`** \u2014 Product created.\n\n```\n{\n "data": {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/products` \u2014 List products\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `status` | query | "active" \\| "inactive" | No | |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Products.\n\n```\n{\n "data": [\n {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/products/{id}` \u2014 Retrieve a product\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n\n**Response `200`** \u2014 The product.\n\n```\n{\n "data": {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `PATCH /v1/checkout/products/{id}` \u2014 Update a product\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n\n**Request body** (`UpdateProductRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | No | 1\u2013200 chars |\n| `description` | string \\| null | No | \u2264 2000 chars |\n| `image_url` | string (uri) \\| null | No | \u2264 2048 chars |\n| `metadata` | map | No | |\n| `status` | "active" \\| "inactive" | No | |\n\n**Response `200`** \u2014 The updated product.\n\n```\n{\n "data": {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/checkout/products/{id}/prices` \u2014 Create a price\n\nPrices are immutable: to change an amount, create a new price. Pass exactly one of `amount` (decimal string) or `amount_atomic` (6-decimal integer string). The currency is derived from `token_symbol`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreatePriceRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `amount` | string | No | Decimal string, e.g. "29". Mutually exclusive with `amount_atomic`. |\n| `amount_atomic` | string | No | 6-decimal integer string ("29000000" = 29). |\n| `token_symbol` | "USDC" \\| "USDT" \\| "JPYC" | No | The price\u2019s asset. The currency is derived from it. (default `"USDC"`) |\n| `billing_type` | "one_time" | No | Only `one_time` for now. (default `"one_time"`) |\n| `nickname` | string | No | Display name. (\u2264 200 chars) |\n| `lookup_key` | string | No | Your own stable key for this price. (1\u2013200 chars) |\n\n```\n{\n "amount": "29",\n "token_symbol": "USDC"\n}\n```\n\n**Response `201`** \u2014 Price created.\n\n```\n{\n "data": {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/products/{id}/prices` \u2014 List a product\u2019s prices\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Prices.\n\n```\n{\n "data": [\n {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/prices/{id}` \u2014 Retrieve a price\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Price id (`price_\u2026`). |\n\n**Response `200`** \u2014 The price.\n\n```\n{\n "data": {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `PATCH /v1/checkout/prices/{id}` \u2014 Update a price\u2019s nickname\n\nOnly `nickname` can change. Sending an amount/currency field returns `PRICE_IMMUTABLE_FIELD`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Price id (`price_\u2026`). |\n\n**Request body** (`UpdatePriceRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `nickname` | string \\| null | No | \u2264 200 chars |\n\n**Response `200`** \u2014 The updated price.\n\n```\n{\n "data": {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n## Payment links\n\nShareable hosted URLs for a price.\n\n### `POST /v1/checkout/payment-links` \u2014 Create a payment link\n\nA shareable hosted URL for one price. The link \u2194 price binding is fixed after creation.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreatePaymentLinkRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `price_id` | string | Yes | The price this link sells (`price_\u2026`). |\n| `title` | string | No | 1\u2013200 chars |\n| `description` | string | No | \u2264 2000 chars |\n| `collect_email` | boolean | No | Ask the buyer for an email. Default true. (default `true`) |\n| `email_required` | boolean | No | Require the email: the hosted link page asks for it and no checkout is created without it. Default true; needs `collect_email`. (default `true`) |\n| `allowed_pay_chains` | array of string | No | Networks the buyer may choose from (CAIP-2 or short names). (1\u20138 items) |\n| `allowed_assets` | array of object | No | Allowed (chain, token) cells. Takes precedence over `allowed_pay_chains`. (1\u201316 items) |\n| `success_url` | string (uri) | No | \u2264 2048 chars |\n| `cancel_url` | string (uri) | No | \u2264 2048 chars |\n| `max_uses` | integer | No | Stop accepting payments after this many paid uses. (\u2265 1) |\n| `expires_at` | string (date-time) | No | ISO-8601 time after which the link stops working. |\n| `metadata` | map | No | |\n\n```\n{\n "price_id": "price_\u2026",\n "allowed_pay_chains": [\n "eip155:8453"\n ]\n}\n```\n\n**Response `201`** \u2014 Link created.\n\n```\n{\n "data": {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/payment-links` \u2014 List payment links\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `status` | query | "active" \\| "inactive" | No | |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Links.\n\n```\n{\n "data": [\n {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/payment-links/{id}` \u2014 Retrieve a payment link\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Payment link id (`plink_\u2026`). |\n\n**Response `200`** \u2014 The link, with product/price summary fields.\n\n```\n{\n "data": {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `PATCH /v1/checkout/payment-links/{id}` \u2014 Update a payment link\n\nOperational settings and active \u2194 inactive. The price cannot change \u2014 create a new link instead.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Payment link id (`plink_\u2026`). |\n\n**Request body** (`UpdatePaymentLinkRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `title` | string \\| null | No | 1\u2013200 chars |\n| `description` | string \\| null | No | \u2264 2000 chars |\n| `collect_email` | boolean | No | |\n| `email_required` | boolean | No | |\n| `allowed_pay_chains` | array of string \\| null | No | 1\u20138 items |\n| `allowed_assets` | array of object \\| null | No | 1\u201316 items |\n| `success_url` | string (uri) \\| null | No | \u2264 2048 chars |\n| `cancel_url` | string (uri) \\| null | No | \u2264 2048 chars |\n| `status` | "active" \\| "inactive" | No | |\n| `max_uses` | integer \\| null | No | \u2265 1 |\n| `expires_at` | string (date-time) \\| null | No | |\n| `metadata` | map | No | |\n\n**Response `200`** \u2014 The updated link.\n\n```\n{\n "data": {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \xB7 `409` \u2014 see [Errors](/docs/md/errors.md).\n\n## x402\n\nCharge per API call with HTTP 402 and EIP-3009 authorizations.\n\n### `GET /v1/x402/assets` \u2014 List x402 assets for this mode\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Response `200`** \u2014 Assets.\n\n```\n{\n "data": {\n "mode": "TEST",\n "assets": [\n {\n "caip2": "string",\n "network": "string",\n "chain_name": "string",\n "symbol": "string",\n "decimals": 0,\n "address": "string"\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/x402/requirements` \u2014 Build the 402 payment requirements\n\nReturns the body your resource server sends with `402 Payment Required`, including the fee split. Pass exactly one of `amount` or `amountAtomic`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`X402RequirementsRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `network` | string | No | x402 network name (`base`, `base-sepolia`). Defaults to the mode\u2019s first asset. (\u2265 1 chars) |\n| `asset` | string | No | Token symbol. Default USDC. (\u2265 1 chars, default `"USDC"`) |\n| `payTo` | string | Yes | Your payout wallet (0x) \u2014 must be the one registered in Console for this mode, otherwise `PAYTO_NOT_MERCHANT_WALLET`. |\n| `amount` | string | No | Gross amount per call as a decimal string, e.g. "0.1". The fee is taken from it. |\n| `amountAtomic` | string | No | Same as `amount`, as a 6-decimal integer string. |\n| `resource` | string | Yes | The URL being paid for. (1\u20132048 chars) |\n| `description` | string | No | \u2264 512 chars |\n| `mimeType` | string | No | \u2264 128 chars |\n| `maxTimeoutSeconds` | integer | No | How long the payer has to sign (max 3600). Default 300. (\u2264 3600, > 0) |\n\n```\n{\n "payTo": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",\n "amount": "0.1",\n "resource": "https://api.example.com/report"\n}\n```\n\n**Response `200`** \u2014 402 body.\n\n```\n{\n "data": {\n "x402Version": 1,\n "accepts": [\n {\n "scheme": "exact",\n "network": "string",\n "asset": "string",\n "payTo": "string",\n "maxAmountRequired": "1000000",\n "resource": "string",\n "description": "string",\n "mimeType": "string",\n "maxTimeoutSeconds": 0,\n "extra": {\n "name": "string",\n "version": "string",\n "chainId": 0\n },\n "feeBreakdown": {\n "netAmount": "1000000",\n "feeAmount": "1000000",\n "feeBps": 0,\n "feeRecipient": "string"\n }\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/x402/verify` \u2014 Verify an X-PAYMENT header\n\nChecks the EIP-3009 authorization against the requirements. Sends nothing on-chain.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`X402PaymentRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `x402Version` | 1 | Yes | |\n| `paymentPayload` | object | Yes | |\n| `paymentRequirements` | object | Yes | |\n\n**Response `200`** \u2014 Valid.\n\n```\n{\n "data": {\n "valid": true,\n "payment_id": "string",\n "status": "string",\n "idempotent": true\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `409` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/x402/settle` \u2014 Settle an x402 payment\n\nBroadcasts the transfer and waits for the receipt. Serve the resource only when `status` is `confirmed`. Settlement is idempotent per authorization \u2014 retry with the same body on 5xx.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`X402PaymentRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `x402Version` | 1 | Yes | |\n| `paymentPayload` | object | Yes | |\n| `paymentRequirements` | object | Yes | |\n\n**Response `200`** \u2014 Settlement result.\n\n```\n{\n "data": {\n "success": true,\n "payment_id": "string",\n "status": "string",\n "tx_hash": "string",\n "fee_tx_hash": "string",\n "network": "string",\n "idempotent": true\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `409` \xB7 `503` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/x402/payments/{id}` \u2014 Retrieve an x402 payment\n\nRecovery path when a settle response was lost \u2014 check here before settling again.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Payment id. |\n\n**Response `200`** \u2014 The payment.\n\n```\n{\n "data": {\n "id": "string",\n "status": "pending",\n "raw_status": "string",\n "resource": "string",\n "chain": "string",\n "asset": "string",\n "decimals": 0,\n "amount": "1000000",\n "gross_amount": "1000000",\n "payer": "string",\n "pay_to": "string",\n "tx_hash": "string",\n "created_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/x402/payments` \u2014 List x402 payments\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `limit` | query | integer | No | \u2264 100, > 0, default `20` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n| `status` | query | string | No | pending \\| confirmed \\| failed \\| needs_review |\n\n**Response `200`** \u2014 Payments.\n\n```\n{\n "data": [\n {\n "id": "string",\n "status": "pending",\n "raw_status": "string",\n "resource": "string",\n "chain": "string",\n "asset": "string",\n "decimals": 0,\n "amount": "1000000",\n "gross_amount": "1000000",\n "payer": "string",\n "pay_to": "string",\n "tx_hash": "string",\n "created_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).\n\n## Webhook endpoints\n\nManage where BitPal delivers signed events.\n\n### `POST /v1/webhooks` \u2014 Create a webhook endpoint\n\n**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403. The response carries the `whsec_\u2026` signing secret \u2014 it is shown only once.\n\n**Auth:** Console login only \u2014 manage this in Console \u2192 Developers \u2192 Webhooks (API keys get 403)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreateWebhookEndpointRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `endpoint_url` | string (uri) | Yes | HTTPS URL that receives events. Private hosts are rejected. |\n| `subscribed_events` | array of string | Yes | Event names to deliver (see the `webhooks` section). (\u2265 1 items) |\n| `retry_policy` | object | No | `max_retries` caps the total delivery attempts (1\u201320, default 10: 3 right away, then 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h). The schedule itself is fixed; `backoff` is ignored. |\n| `dead_letter_destination` | string (uri) \\| null | No | Optional URL for dead-lettered deliveries. |\n\n```\n{\n "endpoint_url": "https://your.store/webhooks/bitpal",\n "subscribed_events": [\n "checkout.session.paid"\n ]\n}\n```\n\n**Response `201`** \u2014 Endpoint created.\n\n```\n{\n "data": {\n "id": "00000000-0000-4000-8000-000000000000",\n "mode": "TEST",\n "endpoint_url": "string",\n "subscribed_events": [\n "string"\n ],\n "retry_policy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "dead_letter_destination": "string",\n "status": "active",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z",\n "secret": "string"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/webhooks` \u2014 List webhook endpoints\n\n**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403. Endpoints of the API key\u2019s environment and mode. Note: list items use camelCase keys.\n\n**Auth:** Console login only \u2014 manage this in Console \u2192 Developers \u2192 Webhooks (API keys get 403)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Endpoints, newest first. `meta` carries total/limit/offset.\n\n```\n{\n "data": [\n {\n "id": "00000000-0000-4000-8000-000000000000",\n "mode": "TEST",\n "endpoint_url": "string",\n "subscribed_events": [\n "string"\n ],\n "retry_policy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "dead_letter_destination": "string",\n "status": "active",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `PATCH /v1/webhooks/{id}` \u2014 Update a webhook endpoint\n\n**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403. Setting `status` to `disabled` removes the endpoint and requires `reason_code` (otherwise 400 `REASON_CODE_REQUIRED`). It is recorded in the audit log.\n\n**Auth:** Console login only \u2014 manage this in Console \u2192 Developers \u2192 Webhooks (API keys get 403)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Webhook endpoint id (uuid). |\n\n**Request body** (`UpdateWebhookEndpointRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `endpoint_url` | string (uri) | No | |\n| `subscribed_events` | array of string | No | |\n| `status` | "active" \\| "paused" \\| "disabled" | No | |\n| `retry_policy` | object | No | |\n| `dead_letter_destination` | string (uri) \\| null | No | |\n| `reason_code` | "user_request" \\| "security_concern" \\| "rotation" \\| "no_longer_needed" \\| "other" | No | Required when `status` is `disabled`: one of user_request, security_concern, rotation, no_longer_needed, other. |\n| `reason_note` | string | No | Optional free text (\u2264 500 chars) stored with the audit record. (\u2264 500 chars) |\n\n**Response `200`** \u2014 The updated endpoint.\n\n```\n{\n "data": {\n "id": "00000000-0000-4000-8000-000000000000",\n "mode": "TEST",\n "endpoint_url": "string",\n "subscribed_events": [\n "string"\n ],\n "retry_policy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "dead_letter_destination": "string",\n "status": "active",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/webhooks/{id}/rotate-secret` \u2014 Rotate the signing secret\n\n**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403. Takes effect immediately. The new secret is returned once \u2014 update your verifier. Requires `reason_code` (400 `REASON_CODE_REQUIRED` if missing); recorded in the audit log.\n\n**Auth:** Console login only \u2014 manage this in Console \u2192 Developers \u2192 Webhooks (API keys get 403)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Webhook endpoint id (uuid). |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`RotateWebhookSecretRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `reason_code` | "user_request" \\| "security_concern" \\| "rotation" \\| "no_longer_needed" \\| "other" | Yes | One of user_request, security_concern, rotation, no_longer_needed, other. |\n| `reason_note` | string | No | Optional free text (\u2264 500 chars) stored with the audit record. (\u2264 500 chars) |\n\n```\n{\n "reason_code": "rotation"\n}\n```\n\n**Response `200`** \u2014 Endpoint with the new secret.\n\n```\n{\n "data": {\n "id": "00000000-0000-4000-8000-000000000000",\n "mode": "TEST",\n "endpoint_url": "string",\n "subscribed_events": [\n "string"\n ],\n "retry_policy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "dead_letter_destination": "string",\n "status": "active",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z",\n "secret": "string"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/developers/webhooks/test` \u2014 Send a test event\n\nPosts one signed sample event to an endpoint (no retries). The envelope id is prefixed `test_`, `data.test` is true and the request carries `X-Webhook-Test: true`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`SendTestWebhookRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `webhook_id` | string (uuid) | Yes | Endpoint id. |\n| `event_type` | string | Yes | Event name to put in the envelope, e.g. checkout.session.paid. (1\u2013200 chars) |\n| `payload` | map | No | Custom `data` object. Defaults to a sample message. |\n\n```\n{\n "webhook_id": "00000000-0000-4000-8000-000000000000",\n "event_type": "checkout.session.paid"\n}\n```\n\n**Response `200`** \u2014 Delivery result.\n\n```\n{\n "data": {\n "delivery_id": "00000000-0000-4000-8000-000000000000",\n "webhook_id": "00000000-0000-4000-8000-000000000000",\n "endpoint_url": "string",\n "event_type": "string",\n "http_status": 0,\n "response_body": "string",\n "error": "string",\n "duration_ms": 0,\n "test": true,\n "sent_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \xB7 `409` \u2014 see [Errors](/docs/md/errors.md).\n\n## Webhook events\n\nEnvelope: `{ id, event, created_at, data }`, signed with `X-Webhook-Signature-256`. Verification and retries: [Webhooks](/docs/md/webhooks.md).\n\n| Event | Fires when | `data` fields |\n| --- | --- | --- |\n| `checkout.session.created` | A session is created. | `session_id`, `amount_total`, `currency`, `late` |\n| `checkout.session.deposit_detected` | Funds seen on-chain, awaiting confirmations. | `session_id`, `amount_total`, `currency`, `late`, `tx_hash`, `amount_received`, `received_token` |\n| `checkout.session.paid` | Confirmed \u2014 safe to fulfil. `late: true` if it arrived after the deadline. | `session_id`, `amount_total`, `currency`, `late`, `tx_hash` |\n| `checkout.session.settled` | Funds swept to your payout wallet. | `session_id`, `amount_total`, `currency`, `late`, `sweep_tx_hash` |\n| `checkout.session.unresolved` | Underpaid, wrong token, or a confirmed deposit reverted by a reorg (`reason`). | `session_id`, `amount_total`, `currency`, `late`, `reason`, `amount_expected`, `amount_received`, `received_token`, `tx_hash` |\n| `checkout.session.expired` | Deadline passed without full payment. | `session_id`, `amount_total`, `currency`, `late`, `reason`, `expired_at`, `source`, `refund_tx_hash` |\n| `checkout.refund.recorded` | A refund was made on-chain (`kind`). | `session_id`, `kind`, `amount`, `token`, `tx_hash`, `refund_address` |\n| `x402.payment.resolved` | An x402 call that settled with `503` reached its outcome. Only fires for those \u2014 the normal path answers inline. | `payment_id`, `status`, `resource`, `amount`, `asset`, `chain`, `tx_hash`, `payer`, `resolved_by` |'
|
|
4071
4071
|
},
|
|
4072
4072
|
{
|
|
4073
4073
|
slug: "errors",
|
|
4074
4074
|
title: "Errors",
|
|
4075
4075
|
url: "https://bitpal.io/docs/#errors",
|
|
4076
4076
|
markdownUrl: "https://bitpal.io/docs/md/errors.md",
|
|
4077
|
-
markdown: '# Errors\n\nHow the API reports failures, and what to do about the ones you are likely to see.\n\n## Response format\n\nSuccessful responses are `{ "data": \u2026 }`. Failed responses use an HTTP status code and
|
|
4077
|
+
markdown: '# Errors\n\nHow the API reports failures, and what to do about the ones you are likely to see.\n\n## Response format\n\nSuccessful responses are `{ "data": \u2026 }`. Failed responses use an HTTP status code and this JSON body:\n\n```\n{\n "error": "checkout not enabled for USDC on eip155:8453", // human-readable\n "code": "FEATURE_NOT_ENABLED", // machine-readable\n "retryable": false,\n "trace_id": "4f1c\u2026" // quote it to support\n}\n```\n\n`error` is a human-readable message and may change \u2014 **branch on `code`** (after the HTTP status), never on the text of `error`. Validation failures add a `details` array. The SDK exposes the same fields:\n\n```\nimport { BitPalError } from \'@bitpal/checkout\';\n\ntry {\n await bitpal.checkout.createSession(params);\n} catch (err) {\n if (err instanceof BitPalError) {\n err.statusCode; // 400, 404, 429, \u2026\n err.code; // \'CHECKOUT_AMOUNT_TOO_LOW\' (when the API sent one)\n err.apiMessage; // the API\'s description\n err.retryable; // true / false / undefined\n err.traceId; // for support\n }\n throw err;\n}\n```\n\n## HTTP status codes\n\n| Status | Meaning | Retry? |\n| --- | --- | --- |\n| `400` | The request is invalid \u2014 fix it. | No |\n| `401` | Missing, invalid, revoked or expired API key, or a key for the other mode. | No |\n| `403` | Not allowed \u2014 for example the merchant account is frozen or suspended. | No |\n| `404` | The object doesn\'t exist in this environment (test and live are separate). | No |\n| `409` | Conflicts with the current state, or the same `Idempotency-Key` is still in progress. | After re-reading state |\n| `410` | Gone \u2014 the session or link has expired, sold out or been deactivated. | No |\n| `422` | `Idempotency-Key` reused with a different body or on a different endpoint. | No \u2014 use a new key |\n| `429` | Rate limited. Honour `Retry-After` (seconds). | Yes, with backoff |\n| `5xx` | BitPal-side problem. | Yes, with backoff (same `Idempotency-Key`) |\n\n## Common error codes\n\n| Code | HTTP | Cause \u2192 fix |\n| --- | --- | --- |\n| `UNAUTHORIZED` | 401 | Bad, missing, revoked or expired `Authorization: Bearer bp_\u2026` key. |\n| `VALIDATION_FAILED` | 400 | Body doesn\'t match the schema \u2014 see `details`. Typical: a line item with both or neither of `amount` / `amount_atomic`. |\n| `MIXED_LINE_ITEMS` | 400 | Raw line items and `price_id` items in one session. Use one kind. |\n| `CURRENCY_MIX_NOT_ALLOWED` | 400 | Line items in different currencies. Use one currency per session. |\n| `CHECKOUT_AMOUNT_TOO_LOW` | 400 | The total is below the minimum of 1.00. |\n| `UNSUPPORTED_CHAIN` \xB7 `UNSUPPORTED_ASSET` \xB7 `UNSUPPORTED_TOKEN` \xB7 `UNSUPPORTED_CURRENCY` | 400 | Unknown chain/token, or a chain not enabled for checkout. See [Chains & contracts](/docs/md/reference.md). |\n| `CHAIN_MODE_MISMATCH` | 400 | A mainnet CAIP-2 id with a test key, or a testnet id with a live key. Use the matching id, or a short name (`base`) that resolves to your key\'s mode. |\n| `ALLOWED_CHAINS_CONFLICT` | 400 | `pay_chain` and `allowed_pay_chains` were both set. Send one. |\n| `FEATURE_NOT_ENABLED` | 400 | That asset is not enabled for checkout on that chain. |\n| `SELLER_WALLET_NOT_CONFIGURED` | 400 | No payout wallet for that network family (EVM / Tron / Solana). Add one in Console \u2192 Settings \u2192 Payout Wallet. |\n| `INVALID_REFUND_ADDRESS` | 400 | The refund address is not valid for the chosen network. |\n| `ASSET_SELECTION_REQUIRED` | 400 | Multi-option session: pass `chain` and `token` when issuing the deposit address. |\n| `ASSET_NOT_OFFERED` | 400 | The `chain`/`token` you passed is not one of the session\'s `payment_options`. |\n| `CHECKOUT_BELOW_MIN_PAYMENT` | 400 | The amount is below the minimum payment for the chosen network (some networks, such as Tron, have a higher floor). Pick another option. |\n| `SESSION_NOT_FOUND` | 404 | Wrong id, or the session belongs to the other environment. |\n| `PRODUCT_NOT_FOUND` \xB7 `PAYMENT_LINK_NOT_FOUND` | 404 | Wrong id or environment. |\n| `PRICE_NOT_FOUND` | 404 / 400 | 404 when you fetch a price by id; 400 when a `price_id` in a session\'s line items or a payment link doesn\'t exist (wrong id or environment). |\n| `INVALID_STATE_TRANSITION` | 409 | The session can\'t do that in its current state \u2014 for example issuing a deposit address for a session that is already paid or expired. Re-read the session; create a new one if it has expired. |\n| `CHECKOUT_SESSION_EXPIRED` | 410 | A deposit address was requested after `expires_at` for a session that never got one. Create a new session. |\n| `LINK_EXPIRED` \xB7 `LINK_INACTIVE` \xB7 `LINK_SOLD_OUT` | 410 | The payment link can\'t take payments any more. |\n| `MERCHANT_FROZEN` \xB7 `MERCHANT_SUSPENDED` | 403 | The account is restricted. Contact support. |\n| `TOO_MANY_ACTIVE_SESSIONS` | 429 | Over 1,000 open sessions. Let unused ones expire, and don\'t create a session before the buyer intends to pay. |\n| `RATE_LIMIT_EXCEEDED` | 429 | Too many requests. Wait `Retry-After` seconds. |\n| `INVALID_IDEMPOTENCY_KEY` | 400 | `Idempotency-Key` must be 8\u2013128 characters. |\n| `IDEMPOTENCY_KEY_REUSED` | 422 | Same key with a different body, or on a different endpoint (method and path are part of the fingerprint). Use a new key for a new request. |\n| `IDEMPOTENCY_IN_PROGRESS` | 409 | The first request with this key is still running. Retry shortly. |\n| `INTERNAL_ERROR` | 500 | Retry with backoff, with the same `Idempotency-Key`; if it persists, contact support with `trace_id`. |\n\n## Retrying safely\n\n- Send an `Idempotency-Key` (8\u2013128 characters, unique per operation \u2014 an order id works) on any merchant `POST` (sessions, products, prices, payment links, webhook endpoints, \u2026) \u2014 it is honoured on all of them. Replays within 24 hours return the original response with `X-Idempotency-Replayed: true`. A key is bound to one request: reusing it with a different body or on a different endpoint returns `422 IDEMPOTENCY_KEY_REUSED`. Failed requests (4xx/5xx) are not cached, so you can retry them with the same key.\n- Retry only `429`, `5xx` and network errors, with exponential backoff.\n- Rate-limited responses carry `Retry-After` and `X-RateLimit-Limit` / `-Remaining` / `-Reset`.'
|
|
4078
4078
|
},
|
|
4079
4079
|
{
|
|
4080
4080
|
slug: "flows",
|
|
4081
4081
|
title: "Popup & headless",
|
|
4082
4082
|
url: "https://bitpal.io/docs/#flows",
|
|
4083
4083
|
markdownUrl: "https://bitpal.io/docs/md/flows.md",
|
|
4084
|
-
markdown: '# Popup & headless\n\nChoose how the buyer sees the payment. Every flow ends the same way: you fulfil from the webhook.\n\n## Choose a flow\n\n| Flow | Buyer experience | You build |\n| --- | --- | --- |\n| **Redirect** | Leaves your site for the hosted page, returns to `success_url`. | Create a session, redirect. Simplest. |\n| **Popup** | Hosted page in a small window; your page stays open. | Open the window, listen for one message. |\n| **Headless** | Your own payment UI: options, address, QR, status. | A few backend endpoints plus your UI. |\n| **Payment link** | A shareable URL; no code. \u2192 [Payment links](/docs/md/links.md) | Nothing. |\n| **x402** | No page \u2014 an API client pays per call. \u2192 [x402](/docs/md/x402.md) | Middleware on the route. |\n\n## Popup\n\nBrowsers block `window.open` unless it runs directly in the click handler. Open an empty window first, then point it at the checkout URL once your backend has created the session.\n\n```\nasync function onPayClick() {\n // 1) open synchronously, inside the click handler\n const popup = window.open(\'\', \'bitpal_checkout\', \'width=460,height=720\');\n\n // 2) your backend creates the session (secret key stays on the server)\n const { checkout_url, session_id } = await fetch(\'/api/checkout\', { method: \'POST\' }).then((r) => r.json());\n\n // 3) navigate the popup \u2014 or fall back to a full redirect if it was blocked\n if (popup && !popup.closed) popup.location.href = checkout_url;\n else window.location.href = checkout_url;\n\n const origin = new URL(checkout_url).origin;\n window.addEventListener(\'message\', function onMessage(e) {\n if (e.origin !== origin) return; // trust only the checkout page\n const m = e.data;\n if (!m || m.source !== \'bitpal-checkout\' || m.type !== \'terminal\') return;\n window.removeEventListener(\'message\', onMessage);\n refreshOrderFromServer(session_id); // m.step: done | expired |
|
|
4084
|
+
markdown: '# Popup & headless\n\nChoose how the buyer sees the payment. Every flow ends the same way: you fulfil from the webhook.\n\n## Choose a flow\n\n| Flow | Buyer experience | You build |\n| --- | --- | --- |\n| **Redirect** | Leaves your site for the hosted page, returns to `success_url`. | Create a session, redirect. Simplest. |\n| **Popup** | Hosted page in a small window; your page stays open. | Open the window, listen for one message. |\n| **Headless** | Your own payment UI: options, address, QR, status. | A few backend endpoints plus your UI. |\n| **Payment link** | A shareable URL; no code. \u2192 [Payment links](/docs/md/links.md) | Nothing. |\n| **x402** | No page \u2014 an API client pays per call. \u2192 [x402](/docs/md/x402.md) | Middleware on the route. |\n\n## Popup\n\nBrowsers block `window.open` unless it runs directly in the click handler. Open an empty window first, then point it at the checkout URL once your backend has created the session.\n\n```\nasync function onPayClick() {\n // 1) open synchronously, inside the click handler\n const popup = window.open(\'\', \'bitpal_checkout\', \'width=460,height=720\');\n\n // 2) your backend creates the session (secret key stays on the server)\n const { checkout_url, session_id } = await fetch(\'/api/checkout\', { method: \'POST\' }).then((r) => r.json());\n\n // 3) navigate the popup \u2014 or fall back to a full redirect if it was blocked\n if (popup && !popup.closed) popup.location.href = checkout_url;\n else window.location.href = checkout_url;\n\n const origin = new URL(checkout_url).origin;\n window.addEventListener(\'message\', function onMessage(e) {\n if (e.origin !== origin) return; // trust only the checkout page\n const m = e.data;\n if (!m || m.source !== \'bitpal-checkout\' || m.type !== \'terminal\') return;\n window.removeEventListener(\'message\', onMessage);\n refreshOrderFromServer(session_id); // m.step: done | expired | error\n });\n}\n```\n\nWhen the payment reaches a final screen, the checkout page posts one message to its opener:\n\n```\n{ "source": "bitpal-checkout", "type": "terminal", "step": "done", "session_id": "cs_\u2026" }\n```\n\n- `step` is `done`, `expired` or `error`. One `terminal` message is sent per final screen.\n- If the buyer sends the wrong token, the page posts `{ "type": "status", "step": "wrong_token" }` instead. It is not final: the wrong token is returned and the session can still be paid with the right one, so keep listening for `terminal`.\n- The message is a **UI hint**. Re-read the order from your server (which learned the result from the webhook) before showing success.\n- Messages can be lost (the buyer closes the window, browser privacy settings). Keep a fallback: poll your own backend for the order status.\n- Don\'t close the popup yourself \u2014 the completion screen lets the buyer email a receipt. The popup closes itself only after the buyer sends that receipt; otherwise the buyer closes it with the **Close** button, so don\'t wait for the window to close.\n\n## Headless (your own UI)\n\nUse this when you render the payment screen yourself. The buyer still pays to a per-order deposit address; you just display it.\n\n> **Call BitPal from your backend.** Browsers on your domain may only call the public session endpoint directly (CORS). Issue the deposit address and poll status through your server, and never ship a secret key to the browser.\n\n1. **Create the session** on your server (as in the Quickstart). Keep `id` and `session_token`.\n2. **Show the payment options.** `getPublicSession(id)` returns `payment_options` \u2014 one entry per allowed (chain, token) with `caip2`, `token_symbol`, `decimals` and the exact `amount_atomic`.\n3. **Collect a refund address** from the buyer, in the format of the chosen network (EVM `0x\u2026`, Tron `T\u2026`, Solana base58). It is committed into the deposit address and cannot change.\n4. **Issue the deposit address** (backend):\n\n ```\n const { data } = await bitpal.checkout.issueDepositAddress(session.id, {\n session_token: session.session_token,\n refundAddress: \'0xBuyerRefundWallet\u2026\',\n chain: \'eip155:84532\',\n token: \'USDC\',\n });\n // {\n // "sessionId": "cs_\u2026",\n // "chain": "eip155:84532",\n // "depositAddress": "0x6b19e871c934aB52775B6712e110f3e4b0626347",\n // "expectedToken": "0x943b94dacf1507f2658f1cfe103ac36ebc13ef65",\n // "expectedAmount": "29000000", // in the token\'s own decimals\n // "feeAmount": "435000",\n // "refundAddress": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",\n // "deadline": "1793957308", // on-chain deadline (unix s) = expiresAt + 5400 (90-minute grace)\n // "expiresAt": "2026-11-06T07:58:28.776Z"\n // }\n ```\n5. **Display** the address, a QR code and the exact amount \u2014 format `expectedAmount` with the token\'s decimals (6 for USDC, 18 for BNB USDT and JPYC). Show the countdown to `expiresAt`.\n6. **Track status** through your backend (`getStatus(id)`) for the UI, and **fulfil from the webhook**.\n\nCalling `issueDepositAddress` again for the same session returns the same address **and keeps the first refund address** \u2014 a later call cannot change it. A deposit address belongs to one order; don\'t reuse it.'
|
|
4085
4085
|
},
|
|
4086
4086
|
{
|
|
4087
4087
|
slug: "lifecycle",
|
|
4088
4088
|
title: "Payment lifecycle",
|
|
4089
4089
|
url: "https://bitpal.io/docs/#lifecycle",
|
|
4090
4090
|
markdownUrl: "https://bitpal.io/docs/md/lifecycle.md",
|
|
4091
|
-
markdown: '# Payment lifecycle\n\nWhat a session goes through, what each state means for you, and how amounts, late payments and refunds work.\n\n## States\n\n```\ncreated \u2500\u2500\u25B6 awaiting_deposit \u2500\u2500\u25B6 deposit_detected \u2500\u2500\u25B6 paid_confirmed \u2500\u2500\u25B6 swept\n \u2502 \u25B2 \u2502 \u2502\n \u2502 \u2502 \u2514\u2500\u25B6 unresolved_underpaid \u2500\u2500(deadline)\u2500\u2500\u25B6 expired (partial refund)\n \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500 wrong_token (wrong asset returned; still payable)\n \u2514\u2500\u2500(deadline, nothing paid)\u2500\u2500\u25B6 expired\n```\n\n| Status | Meaning | What you do |\n| --- | --- | --- |\n| `created` | Session exists; the buyer has not picked a payment option yet. | Nothing. |\n| `awaiting_deposit` | A deposit address was issued; waiting for funds. | Nothing. |\n| `deposit_detected` | Funds seen on-chain, waiting for confirmations. | Show "processing". **Don\'t fulfil yet.** |\n| `paid_confirmed` | The full amount is confirmed. | **Fulfil** (webhook `checkout.session.paid`). |\n| `swept` | Funds moved to your payout wallet, minus the fee. | Reconcile (webhook `checkout.session.settled`). |\n| `unresolved_underpaid` | Less than the amount arrived. The buyer can top up until the deadline. | Wait. If it is still short at the deadline, it is refunded and the session expires. |\n| `wrong_token` | A different token was sent. It is returned to the refund address; the session stays payable. | Nothing. |\n| `expired` | The payment window closed without a full payment. | Release the order. A full payment can still arrive later \u2014 see Late payments. |\n\n> **Chain reorganisations.** Rarely, a confirmed deposit disappears after a chain reorg. You then receive `checkout.session.unresolved` with `reason: "reorg"` and the session goes back to `awaiting_deposit` (or `expired` past the deadline). If you have already fulfilled, treat it like a reversed payment.\n\n## Amounts\n\n| Field | Unit |\n| --- | --- |\n| `line_items[].amount` (input) | Plain number string in the line item\'s `currency`: `"29"`, `"0.5"`. `USDC`/`USDT` = USD, `JPYC` = JPY. No FX conversion. |\n| `amount_atomic` (input), `amount_total` | Always a **6-decimal integer string**, whatever token the buyer pays with: `"29000000"` = 29. |\n| `expectedAmount`, `pay_amount`, `amount_received`, webhook `amount_expected`/`amount_received`, refund `amount` | Integer string in the **pay token\'s own decimals** (`token_decimals`: 6 for USDC/USDT on most chains, 18 for BNB USDT and JPYC). |\n| `fee_amount`, `net_amount` | 6-decimal integer strings; `fee_rate` is a decimal fraction (`"0.015"` = 1.5%). |\n\nHandle all amounts as strings or big integers \u2014 never as floating-point numbers.\n\n## Payment window and late payments\n\n- The buyer has `expires_in_minutes` (1\u201360, default 15) from session creation to pay.\n- **A full payment that arrives after the deadline is accepted as a late payment** for up to 7 days: you get `checkout.session.paid` with `"late": true` and the funds settle normally. Decide whether to fulfil, or refund the buyer yourself.\n- A **partial** payment after the deadline is refunded automatically (refund kind `late_deposit`).\n- Funds sent more than 7 days after the deadline are not watched automatically. Contact support with the session id and transaction hash.\n\n## Refunds\n\nBitPal never holds funds, so mismatched payments are returned on-chain to the buyer\'s refund address \u2014 automatically, with no action from you. Each refund fires `checkout.refund.recorded`.\n\n| `kind` | When | Session outcome |\n| --- | --- | --- |\n| `overpay_excess` | More than the amount arrived. | Paid; the exact amount settles, the excess is refunded. |\n| `underpay_expired` | Still short at the deadline. | Expired; everything received is refunded. |\n| `wrong_token` | A token other than the selected one was sent. | Unchanged; still payable with the right token. |\n| `late_deposit` | A partial payment arrived after the deadline. | Stays expired. |\n\nRefunding a completed, correct payment (for example a customer return) is up to you: send the funds from your payout wallet.'
|
|
4091
|
+
markdown: '# Payment lifecycle\n\nWhat a session goes through, what each state means for you, and how amounts, late payments and refunds work.\n\n## States\n\n```\ncreated \u2500\u2500\u25B6 awaiting_deposit \u2500\u2500\u25B6 deposit_detected \u2500\u2500\u25B6 paid_confirmed \u2500\u2500\u25B6 swept\n \u2502 \u25B2 \u2502 \u2502\n \u2502 \u2502 \u2514\u2500\u25B6 unresolved_underpaid \u2500\u2500(deadline)\u2500\u2500\u25B6 expired (partial refund)\n \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500 wrong_token (wrong asset returned; still payable)\n \u2514\u2500\u2500(deadline, nothing paid)\u2500\u2500\u25B6 expired\n```\n\n| Status | Meaning | What you do |\n| --- | --- | --- |\n| `created` | Session exists; the buyer has not picked a payment option yet. | Nothing. |\n| `awaiting_deposit` | A deposit address was issued; waiting for funds. | Nothing. |\n| `deposit_detected` | Funds seen on-chain, waiting for confirmations. | Show "processing". **Don\'t fulfil yet.** |\n| `paid_confirmed` | The full amount is confirmed. | **Fulfil** (webhook `checkout.session.paid`). |\n| `swept` | Funds moved to your payout wallet, minus the fee. | Reconcile (webhook `checkout.session.settled`). |\n| `unresolved_underpaid` | Less than the amount arrived. The buyer can top up until the deadline. | Wait. If it is still short at the deadline, it is refunded and the session expires. |\n| `wrong_token` | A different token was sent. It is returned to the refund address; the session stays payable. | Nothing. |\n| `expired` | The payment window closed without a full payment. | Release the order. A full payment can still arrive later \u2014 see Late payments. |\n\n> **Chain reorganisations.** Rarely, a confirmed deposit disappears after a chain reorg. You then receive `checkout.session.unresolved` with `reason: "reorg"` and the session goes back to `awaiting_deposit` (or `expired` past the deadline). If you have already fulfilled, treat it like a reversed payment.\n\n## Amounts\n\n| Field | Unit |\n| --- | --- |\n| `line_items[].amount` (input) | Plain number string in the line item\'s `currency`: `"29"`, `"0.5"`. `USDC`/`USDT` = USD, `JPYC` = JPY. No FX conversion. |\n| `amount_atomic` (input), `amount_total` | Always a **6-decimal integer string**, whatever token the buyer pays with: `"29000000"` = 29. |\n| `expectedAmount`, `pay_amount`, `amount_received`, webhook `amount_expected`/`amount_received`, refund `amount` | Integer string in the **pay token\'s own decimals** (`token_decimals`: 6 for USDC/USDT on most chains, 18 for BNB USDT and JPYC). |\n| `fee_amount`, `net_amount` | 6-decimal integer strings; `fee_rate` is a decimal fraction (`"0.015"` = 1.5%). |\n\nHandle all amounts as strings or big integers \u2014 never as floating-point numbers.\n\n## Payment window and late payments\n\n- The buyer has `expires_in_minutes` (1\u201360, default 15) from session creation to pay. That deadline is the session\'s `expires_at`.\n- `late` compares the **block time of the deposit transaction** with `expires_at`. A transfer sent in time is `"late": false` even if its confirmations finish after the deadline; `checkout.session.settled` carries the same value as `paid`.\n- Once a deposit address has been issued, BitPal keeps the session open for **90 minutes after `expires_at`** so transfers already in flight can confirm. Underpayment refunds and `checkout.session.expired` for an issued session happen at the end of that window.\n- **A full payment that arrives after the deadline is accepted as a late payment** for up to 7 days: you get `checkout.session.paid` with `"late": true` and the funds settle normally. Decide whether to fulfil, or refund the buyer yourself.\n- This only applies to a session with no earlier on-chain action. If a wrong token was already returned or an underpayment already refunded, the expired session is closed for good: later deposits to its address are not accepted as payment (contact support with the session id and transaction hash).\n- Acceptance ends 1 hour before the on-chain refund window opens (`expires_at` + 90 minutes + 7 days). A full payment that arrives after that cutoff is not accepted; it is refunded automatically once the window opens (refund kind `late_deposit`).\n- A **partial** payment after the deadline is refunded automatically (refund kind `late_deposit`).\n- Funds sent more than 7 days after the deadline are not watched automatically. Contact support with the session id and transaction hash.\n\n## Refunds\n\nBitPal never holds funds, so mismatched payments are returned on-chain to the buyer\'s refund address \u2014 automatically, with no action from you. Each refund fires `checkout.refund.recorded`.\n\n| `kind` | When | Session outcome |\n| --- | --- | --- |\n| `overpay_excess` | More than the amount arrived. | Paid; the exact amount settles, the excess is refunded. |\n| `underpay_expired` | Still short at the deadline. | Expired; everything received is refunded. |\n| `wrong_token` | A token other than the selected one was sent. | Unchanged; still payable with the right token. |\n| `late_deposit` | A partial payment arrived after the deadline, or a full payment arrived after the late-acceptance cutoff (see above). | Stays expired. |\n\nRefunding a completed, correct payment (for example a customer return) is up to you: send the funds from your payout wallet.'
|
|
4092
4092
|
},
|
|
4093
4093
|
{
|
|
4094
4094
|
slug: "links",
|
|
@@ -4109,35 +4109,35 @@ var docs_data_default = {
|
|
|
4109
4109
|
title: "Quickstart",
|
|
4110
4110
|
url: "https://bitpal.io/docs/#quickstart",
|
|
4111
4111
|
markdownUrl: "https://bitpal.io/docs/md/quickstart.md",
|
|
4112
|
-
markdown: '# Quickstart\n\nTake one test payment end to end: create a session, pay it on a testnet, and receive the signed webhook. About ten minutes.\n\n## Before you start\n\n1. **Console account in test mode.** Everything below uses testnets and test keys \u2014 no real funds.\n2. **Payout wallet.** Console \u2192 Settings \u2192 **Payout Wallet**. Each deposit address is committed to this wallet, so a session cannot issue an address without one. Register an address for every network family you accept (EVM `0x\u2026`, Tron `T\u2026`, Solana).\n3. **Test API key.** Console \u2192 Developers \u2192 **API Keys** \u2192 create a `bp_test_\u2026` key. It is shown once. Keep it on your server \u2014 never in a browser or app bundle.\n4. **A testnet wallet with a little gas** (for example Base Sepolia ETH) to send the payment. The checkout page\'s test faucet mints test tokens, not gas.\n\n## 1. Create a checkout session\n\nInstall the SDK, or call the REST API directly.\n\n```\nnpm install @bitpal/checkout\n```\n\n```\nimport { BitPal } from \'@bitpal/checkout\';\n\nconst bitpal = new BitPal(process.env.BITPAL_API_KEY); // bp_test_\u2026\n\nconst { data: session } = await bitpal.checkout.createSession({\n line_items: [{ name: \'Pro plan\', amount: \'29\', currency: \'USDC\' }],\n pay_chain: \'eip155:84532\', // Base Sepolia (test key \u2192 testnet values only)\n expires_in_minutes: 30, // payment window, 1\u201360 (default 15)\n success_url: \'https://your.store/thanks?session={CHECKOUT_SESSION_ID}\',\n});\n// store session.id with your order, then send the buyer to session.checkout_url\n```\n\nThe same request over HTTP:\n\n```\ncurl -X POST https://api.bitpal.io/v1/checkout/sessions \\\n -H "Authorization: Bearer $BITPAL_API_KEY" \\\n -H "Content-Type: application/json" \\\n -H "Idempotency-Key: order-1024" \\\n -d \'{\n "line_items": [{ "name": "Pro plan", "amount": "29", "currency": "USDC" }],\n "pay_chain": "eip155:84532",\n "expires_in_minutes": 30,\n "success_url": "https://your.store/thanks?session={CHECKOUT_SESSION_ID}"\n }\'\n```\n\nResponse `201`:\n\n```\n{\n "data": {\n "id": "cs_Q2FxKhomCEju_OqvKfrQsTOh",\n "checkout_url": "https://bitpal.io/pay/cs_Q2FxKhomCEju_OqvKfrQsTOh?token=<session_token>",\n "session_token": "<session_token>",\n "status": "created",\n "amount_total": "29000000",\n "currency": "USDC",\n "expires_at": "2026-11-06T07:58:28.776Z",\n "tax": { "tax_amount": "0", "total_with_tax": "29000000" }\n },\n "meta": { "mode": "TEST", "requestId": "ec091faf-\u2026" }\n}\n```\n\n- `amount` is a plain number string in the line item\'s currency \u2014 no decimals to compute. `amount_total` comes back as a 6-decimal integer string (`"29000000"` = 29).\n- `Idempotency-Key` (8\u2013128 chars) makes retries safe: the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`.\n- `{CHECKOUT_SESSION_ID}` in `success_url` is replaced with the session id when the buyer returns.\n\n## 2. Send the buyer to the checkout page\n\nRedirect to `checkout_url`, or open it in a popup so the buyer stays on your page (see [Popup & headless](/docs/md/flows.md)). On the page the buyer:\n\n1. picks a network and token (if you allowed more than one),\n2. enters a **refund address** \u2014 over/under-payments are returned there,\n3. gets a one-time **deposit address** and the exact amount, and sends it from any wallet or exchange.\n\nIn test mode the page shows **Get test assets**: it mints test tokens to the buyer\'s wallet so you can complete a real on-chain payment.\n\n> **No testnet wallet?** Console \u2192 Developers \u2192 **Sandbox** creates a session and sends the deposit for you \u2014 pay in full, underpay, overpay or send the wrong token \u2014 and shows every webhook event as your endpoint receives it. Test mode only; 10 simulations per minute and 300 per day per account.\n\n## 3. Receive the webhook\n\nConsole \u2192 Developers \u2192 Webhooks \u2192 **Add Endpoint**. The URL must be publicly reachable over HTTPS (for local development use a tunnel such as ngrok or cloudflared). Copy the signing secret (`whsec_\u2026`) \u2014 it is shown once.\n\n```\nimport express from \'express\';\nimport { verifyWebhookSignature, parseWebhookEvent, WEBHOOK_HEADERS } from \'@bitpal/checkout\';\n\nconst app = express();\n\napp.post(\'/webhooks/bitpal\', express.raw({ type: \'application/json\' }), async (req, res) => {\n const raw = req.body.toString(\'utf8\'); // exact bytes BitPal signed\n const ok = await verifyWebhookSignature( // async \u2014 always await it\n raw,\n req.header(WEBHOOK_HEADERS.signature) ?? \'\',\n process.env.BITPAL_WEBHOOK_SECRET, // whsec_\u2026\n { timestamp: req.header(WEBHOOK_HEADERS.timestamp) ?? \'\' },\n );\n if (!ok) return res.status(400).send(\'invalid signature\');\n\n const event = parseWebhookEvent(raw);\n if (await alreadyProcessed(event.id)) return res.sendStatus(200); // at-least-once delivery\n\n if (event.event === \'checkout.session.paid\') {\n await fulfilOrder(event.data.session_id, { late: event.data.late === true });\n }\n await markProcessed(event.id);\n res.sendStatus(200); // any 2xx within 10 s = delivered\n});\n```\n\nFor one successful payment you receive, in order: `checkout.session.created` \u2192 `checkout.session.deposit_detected` \u2192 `checkout.session.paid` \u2192 `checkout.session.settled`. Use **Send Test** in the console to check your endpoint before paying.\n\n## 4. Fulfil on `checkout.session.paid`\n\n- `paid` means the deposit reached the required confirmations \u2014 safe to fulfil. `settled` follows when funds are swept to your payout wallet.\n- Treat the webhook (or a server-side `getSession(id)`) as the source of truth. The redirect to `success_url` and popup messages are hints for your UI only.\n- Check `data.late`: `true` means the full amount arrived after the payment window (accepted for up to 7 days). Decide whether to fulfil or refund it yourself.\n\n## Going live\n\n1. Create a `bp_live_\u2026` key and switch chain values to mainnet ([Chains & contracts](/docs/md/reference.md)).\n2. Register live payout wallets \u2014 test and live are separate environments.\n3. Add the webhook endpoint again in live mode. It gets its own `whsec_\u2026` secret.\n4. Handle the non-happy paths: `checkout.session.unresolved`, `checkout.session.expired`, `checkout.refund.recorded` and late payments ([Payment lifecycle](/docs/md/lifecycle.md)).'
|
|
4112
|
+
markdown: '# Quickstart\n\nTake one test payment end to end: create a session, pay it on a testnet, and receive the signed webhook. About ten minutes.\n\n## Before you start\n\n1. **Console account in test mode.** Everything below uses testnets and test keys \u2014 no real funds.\n2. **Payout wallet.** Console \u2192 Settings \u2192 **Payout Wallet**. Each deposit address is committed to this wallet, so a session cannot issue an address without one. Register an address for every network family you accept (EVM `0x\u2026`, Tron `T\u2026`, Solana).\n3. **Test API key.** Console \u2192 Developers \u2192 **API Keys** \u2192 create a `bp_test_\u2026` key. It is shown once. Keep it on your server \u2014 never in a browser or app bundle.\n4. **A testnet wallet with a little gas** (for example Base Sepolia ETH) to send the payment. The checkout page\'s test faucet mints test tokens, not gas.\n\n## 1. Create a checkout session\n\nInstall the SDK, or call the REST API directly.\n\n```\nnpm install @bitpal/checkout\n```\n\n```\nimport { BitPal } from \'@bitpal/checkout\';\n\nconst bitpal = new BitPal(process.env.BITPAL_API_KEY); // bp_test_\u2026\n\nconst { data: session } = await bitpal.checkout.createSession({\n line_items: [{ name: \'Pro plan\', amount: \'29\', currency: \'USDC\' }],\n pay_chain: \'eip155:84532\', // Base Sepolia (test key \u2192 testnet values only)\n expires_in_minutes: 30, // payment window, 1\u201360 (default 15)\n success_url: \'https://your.store/thanks?session={CHECKOUT_SESSION_ID}\',\n});\n// store session.id with your order, then send the buyer to session.checkout_url\n```\n\nThe same request over HTTP:\n\n```\ncurl -X POST https://api.bitpal.io/v1/checkout/sessions \\\n -H "Authorization: Bearer $BITPAL_API_KEY" \\\n -H "Content-Type: application/json" \\\n -H "Idempotency-Key: order-1024" \\\n -d \'{\n "line_items": [{ "name": "Pro plan", "amount": "29", "currency": "USDC" }],\n "pay_chain": "eip155:84532",\n "expires_in_minutes": 30,\n "success_url": "https://your.store/thanks?session={CHECKOUT_SESSION_ID}"\n }\'\n```\n\nResponse `201`:\n\n```\n{\n "data": {\n "id": "cs_Q2FxKhomCEju_OqvKfrQsTOh",\n "checkout_url": "https://bitpal.io/pay/cs_Q2FxKhomCEju_OqvKfrQsTOh?token=<session_token>",\n "session_token": "<session_token>",\n "status": "created",\n "amount_total": "29000000",\n "currency": "USDC",\n "expires_at": "2026-11-06T07:58:28.776Z",\n "tax": { "tax_amount": "0", "total_with_tax": "29000000" }\n },\n "meta": { "mode": "TEST", "requestId": "ec091faf-\u2026" }\n}\n```\n\n- `amount` is a plain number string in the line item\'s currency \u2014 no decimals to compute. `amount_total` comes back as a 6-decimal integer string (`"29000000"` = 29).\n- `Idempotency-Key` (8\u2013128 chars) makes retries safe: the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`.\n- `{CHECKOUT_SESSION_ID}` in `success_url` is replaced with the session id when the buyer returns.\n\n## 2. Send the buyer to the checkout page\n\nRedirect to `checkout_url`, or open it in a popup so the buyer stays on your page (see [Popup & headless](/docs/md/flows.md)). On the page the buyer:\n\n1. picks a network and token (if you allowed more than one),\n2. enters a **refund address** \u2014 over/under-payments are returned there,\n3. gets a one-time **deposit address** and the exact amount, and sends it from any wallet or exchange.\n\nIn test mode the page shows **Get test assets**: it mints test tokens to the buyer\'s wallet so you can complete a real on-chain payment.\n\n> **No testnet wallet?** Console \u2192 Developers \u2192 **Sandbox** creates a session and sends the deposit for you \u2014 pay in full, underpay, overpay or send the wrong token \u2014 and shows every webhook event as your endpoint receives it. Test mode only; 10 simulations per minute and 300 per day per account.\n\n## 3. Receive the webhook\n\nConsole \u2192 Developers \u2192 Webhooks \u2192 **Add Endpoint**. The URL must be publicly reachable over HTTPS (for local development use a tunnel such as ngrok or cloudflared). Copy the signing secret (`whsec_\u2026`) \u2014 it is shown once.\n\n```\nimport express from \'express\';\nimport { verifyWebhookSignature, parseWebhookEvent, WEBHOOK_HEADERS } from \'@bitpal/checkout\';\n\nconst app = express();\n\napp.post(\'/webhooks/bitpal\', express.raw({ type: \'application/json\' }), async (req, res) => {\n const raw = req.body.toString(\'utf8\'); // exact bytes BitPal signed\n const ok = await verifyWebhookSignature( // async \u2014 always await it\n raw,\n req.header(WEBHOOK_HEADERS.signature) ?? \'\',\n process.env.BITPAL_WEBHOOK_SECRET, // whsec_\u2026\n { timestamp: req.header(WEBHOOK_HEADERS.timestamp) ?? \'\' },\n );\n if (!ok) return res.status(400).send(\'invalid signature\');\n\n const event = parseWebhookEvent(raw);\n if (await alreadyProcessed(event.id)) return res.sendStatus(200); // at-least-once delivery\n\n if (event.event === \'checkout.session.paid\') {\n await fulfilOrder(event.data.session_id, { late: event.data.late === true });\n }\n await markProcessed(event.id);\n res.sendStatus(200); // any 2xx within 10 s = delivered\n});\n```\n\nFor one successful payment you receive, in order: `checkout.session.created` \u2192 `checkout.session.deposit_detected` \u2192 `checkout.session.paid` \u2192 `checkout.session.settled`. `deposit_detected` is not guaranteed: `paid` can arrive without it (a late payment, or a deposit that was already fully confirmed when BitPal first saw it), so never wait for it before fulfilling. Use **Send Test** in the console to check your endpoint before paying.\n\n## 4. Fulfil on `checkout.session.paid`\n\n- `paid` means the deposit reached the required confirmations \u2014 safe to fulfil. `settled` follows when funds are swept to your payout wallet.\n- Treat the webhook (or a server-side `getSession(id)`) as the source of truth. The redirect to `success_url` and popup messages are hints for your UI only.\n- Check `data.late`: `true` means the full amount arrived after the payment window (accepted for up to 7 days). Decide whether to fulfil or refund it yourself.\n\n## Going live\n\n1. Create a `bp_live_\u2026` key and switch chain values to mainnet ([Chains & contracts](/docs/md/reference.md)).\n2. Register live payout wallets \u2014 test and live are separate environments.\n3. Add the webhook endpoint again in live mode. It gets its own `whsec_\u2026` secret.\n4. Handle the non-happy paths: `checkout.session.unresolved`, `checkout.session.expired`, `checkout.refund.recorded` and late payments ([Payment lifecycle](/docs/md/lifecycle.md)).'
|
|
4113
4113
|
},
|
|
4114
4114
|
{
|
|
4115
4115
|
slug: "reference",
|
|
4116
4116
|
title: "Chains & contracts",
|
|
4117
4117
|
url: "https://bitpal.io/docs/#reference",
|
|
4118
4118
|
markdownUrl: "https://bitpal.io/docs/md/reference.md",
|
|
4119
|
-
markdown: "# Chains & contracts\n\n## Chains & identifiers\n\nThe `pay_chain` / `allowed_pay_chains` values are [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) identifiers. Use the value from the column matching your API key's mode.\n\n| Network | Tokens | Decimals | `pay_chain` \u2014 mainnet | `pay_chain` \u2014 testnet |\n| --- | --- | --- | --- | --- |\n| **Base** | USDC | 6 | `eip155:8453` | `eip155:84532` |\n| **Arbitrum** | USDC | 6 | `eip155:42161` | `eip155:421614` |\n| **BNB Smart Chain** | USDT | 18 | `eip155:56` | `eip155:97` |\n| **Tron** | USDT | 6 | `tron:0x2b6653dc` | `tron:0xcd8690dc` |\n| **Solana** | USDC \xB7 USDT | 6 | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` |\n| **Polygon** | JPYC | 18 | `eip155:137` | `eip155:80002` |\n| **Kaia** | JPYC | 18 | `eip155:8217` | `eip155:1001` |\n\n> JPYC (Polygon/Kaia) settles 1:1 to JPY \u2014 no FX conversion.\n\n> Test keys (`bp_test_\u2026`) accept only testnet values; live keys only mainnet.\n\n## Contract addresses\n\nThe deposit-address contracts (EVM/Tron deterministic forwarder factory, Solana program). Published here as each network goes live.\n\n| Network | Contract / program | Mainnet | Testnet |\n| --- | --- | --- | --- |\n| Base | Forwarder factory | `0xC5d358f4346d59D8eCDb5cE7DE77367b068a7E7c` | `0x5bA4F84B3002B9F94670f296134aE4f823662B9f` |\n| Arbitrum | Forwarder factory | `0xC7989977010890AE78938a84E487573a1D993366` | `0x38B54b7F8a032E8087cd04f12a7Bdb9011c5Bd95` |\n| BNB | Forwarder factory | `0xC7989977010890AE78938a84E487573a1D993366` | `0xeD24d9ED87a9937528520413891e8f78EF7ED3C5` |\n| Tron | Forwarder factory | `TH2t7eXXVbmHxoBvWk5YTAGVtMTomcv6ie` | `TBRQHfn5cRiEd2htYF7Xwae61RZD32T4ee` |\n| Solana | Program ID | `2cZyQxMgWToiCWho9q24VyAvs6xy7t3JSEEwNpGjf4ec` | `2cZyQxMgWToiCWho9q24VyAvs6xy7t3JSEEwNpGjf4ec` |\n| Polygon | Forwarder factory | `0xA2F1bB858DBc25355D8cb5180D7e58bDC2D696De` | `0x42A70E1a47C9AAeF10923f5345b2ad64D2F05D3F` |\n| Kaia | Forwarder factory | `0xA2F1bB858DBc25355D8cb5180D7e58bDC2D696De` | `0xf07BB5243636D8C834b31f224E2567E6e6aF38ce` |\n\nThese are the factories that derive deposit addresses. Always treat the deposit address returned by BitPal as authoritative for a given order \u2014 funds are cryptographically committed to your destination.\n\n## Refunds & edge cases\n\nPayments are push transfers, so a buyer can pay the wrong amount or token. BitPal resolves each case automatically to the buyer's refund address (set at pay time, committed & immutable).\n\n| Case | What happens |\n| --- | --- |\n| **Overpaid** | Settles the exact amount; surplus refunded. |\n| **Underpaid** | Open for a top-up; refunded if the deadline passes. |\n| **Wrong token** | Returned to the buyer; order stays open. |\n| **Expired** | No funds arrived \u2014 address reclaimed. |\n| **Late, full amount** | Accepted as a late payment for up to 7 days (`late: true`); settles normally. |\n| **Late, partial** | Refunded (`late_deposit`). |\n\n## Test mode & faucet\n\n- Keys are environment-scoped: `bp_test_\u2026` on testnets, `bp_live_\u2026` on mainnet.\n- In test mode the hosted page shows a **faucet** \u2014 the buyer mints mock tokens to run a real end-to-end payment. (Gas is separate; the faucet sends tokens only.)"
|
|
4119
|
+
markdown: "# Chains & contracts\n\n## Chains & identifiers\n\nThe `pay_chain` / `allowed_pay_chains` values are [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) identifiers. Use the value from the column matching your API key's mode.\n\n| Network | Tokens | Decimals | `pay_chain` \u2014 mainnet | `pay_chain` \u2014 testnet |\n| --- | --- | --- | --- | --- |\n| **Base** | USDC | 6 | `eip155:8453` | `eip155:84532` |\n| **Arbitrum** | USDC | 6 | `eip155:42161` | `eip155:421614` |\n| **BNB Smart Chain** | USDT | 18 | `eip155:56` | `eip155:97` |\n| **Tron** | USDT | 6 | `tron:0x2b6653dc` | `tron:0xcd8690dc` |\n| **Solana** | USDC \xB7 USDT | 6 | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` |\n| **Polygon** | JPYC | 18 | `eip155:137` | `eip155:80002` |\n| **Kaia** | JPYC | 18 | `eip155:8217` | `eip155:1001` |\n\n> JPYC (Polygon/Kaia) settles 1:1 to JPY \u2014 no FX conversion.\n\n> Test keys (`bp_test_\u2026`) accept only testnet values; live keys only mainnet.\n\n## Contract addresses\n\nThe deposit-address contracts (EVM/Tron deterministic forwarder factory, Solana program). Published here as each network goes live.\n\n| Network | Contract / program | Mainnet | Testnet |\n| --- | --- | --- | --- |\n| Base | Forwarder factory | `0xC5d358f4346d59D8eCDb5cE7DE77367b068a7E7c` | `0x5bA4F84B3002B9F94670f296134aE4f823662B9f` |\n| Arbitrum | Forwarder factory | `0xC7989977010890AE78938a84E487573a1D993366` | `0x38B54b7F8a032E8087cd04f12a7Bdb9011c5Bd95` |\n| BNB | Forwarder factory | `0xC7989977010890AE78938a84E487573a1D993366` | `0xeD24d9ED87a9937528520413891e8f78EF7ED3C5` |\n| Tron | Forwarder factory | `TH2t7eXXVbmHxoBvWk5YTAGVtMTomcv6ie` | `TBRQHfn5cRiEd2htYF7Xwae61RZD32T4ee` |\n| Solana | Program ID | `2cZyQxMgWToiCWho9q24VyAvs6xy7t3JSEEwNpGjf4ec` | `2cZyQxMgWToiCWho9q24VyAvs6xy7t3JSEEwNpGjf4ec` |\n| Polygon | Forwarder factory | `0xA2F1bB858DBc25355D8cb5180D7e58bDC2D696De` | `0x42A70E1a47C9AAeF10923f5345b2ad64D2F05D3F` |\n| Kaia | Forwarder factory | `0xA2F1bB858DBc25355D8cb5180D7e58bDC2D696De` | `0xf07BB5243636D8C834b31f224E2567E6e6aF38ce` |\n\nThese are the factories that derive deposit addresses. Always treat the deposit address returned by BitPal as authoritative for a given order \u2014 funds are cryptographically committed to your destination.\n\n## Refunds & edge cases\n\nPayments are push transfers, so a buyer can pay the wrong amount or token. BitPal resolves each case automatically to the buyer's refund address (set at pay time, committed & immutable).\n\n| Case | What happens |\n| --- | --- |\n| **Overpaid** | Settles the exact amount; surplus refunded. |\n| **Underpaid** | Open for a top-up; refunded if the deadline passes. |\n| **Wrong token** | Returned to the buyer; order stays open. |\n| **Expired** | No funds arrived \u2014 address reclaimed. |\n| **Late, full amount** | Accepted as a late payment for up to 7 days (`late: true`); settles normally. Not for sessions that already had a wrong-token return or underpayment refund; after the cutoff it is refunded (`late_deposit`). |\n| **Late, partial** | Refunded (`late_deposit`). |\n\n## Test mode & faucet\n\n- Keys are environment-scoped: `bp_test_\u2026` on testnets, `bp_live_\u2026` on mainnet.\n- In test mode the hosted page shows a **faucet** \u2014 the buyer mints mock tokens to run a real end-to-end payment. (Gas is separate; the faucet sends tokens only.)"
|
|
4120
4120
|
},
|
|
4121
4121
|
{
|
|
4122
4122
|
slug: "sdk",
|
|
4123
4123
|
title: "SDK",
|
|
4124
4124
|
url: "https://bitpal.io/docs/#sdk",
|
|
4125
4125
|
markdownUrl: "https://bitpal.io/docs/md/sdk.md",
|
|
4126
|
-
markdown: "# SDK\n\nCreate checkout sessions from your backend and redirect buyers to the hosted page.\n\n## Install & create a session\n\n```\nnpm install @bitpal/checkout\n```\n\n```\nimport { BitPal } from '@bitpal/checkout';\nconst bitpal = new BitPal(process.env.BITPAL_API_KEY); // bp_test_\u2026 or bp_live_\u2026\n\nconst { data: session } = await bitpal.checkout.createSession({\n line_items: [{ name: 'Pro plan', amount: '29', currency: 'USDC' }],\n pay_chain: '
|
|
4126
|
+
markdown: "# SDK\n\nCreate checkout sessions from your backend and redirect buyers to the hosted page.\n\n## Install & create a session\n\n```\nnpm install @bitpal/checkout\n```\n\n```\nimport { BitPal } from '@bitpal/checkout';\nconst bitpal = new BitPal(process.env.BITPAL_API_KEY); // bp_test_\u2026 or bp_live_\u2026\n\n// A CAIP-2 id is absolute: 'eip155:8453' (Base mainnet) works only with a live key \u2014\n// with a test key it fails with CHAIN_MODE_MISMATCH. Use 'eip155:84532' for test, or a short name.\nconst { data: session } = await bitpal.checkout.createSession({\n line_items: [{ name: 'Pro plan', amount: '29', currency: 'USDC' }],\n pay_chain: 'base', // short name \u2192 Base Sepolia with bp_test_, Base with bp_live_\n expires_in_minutes: 30, // payment window 1\u201360, default 15\n success_url: 'https://your.store/thanks?s={CHECKOUT_SESSION_ID}',\n});\nredirect(session.checkout_url);\n```\n\n## Amounts \u2014 just pass the number\n\nSet `amount` to a plain number (`\"29\"`, `\"0.5\"`) in the line item's `currency`. No decimals to compute.\n\n> **You never compute token decimals.** The amount is quoted in the line item's `currency` \u2014 `USDC`/`USDT` mean USD, `JPYC` means JPY (no FX conversion). It is independent of chain/token decimals: USDC (6dp) on Base or USDT (18dp) on BNB take the same `amount`, and BitPal rescales at deposit time. (Already have 6-decimal integers? Pass `amount_atomic` instead \u2014 the two are mutually exclusive.)\n\n## Example: buyer chooses chain & token\n\n```\nawait bitpal.checkout.createSession({\n line_items: [{ name: 'Order #1024', amount: '49.90', currency: 'USDC' }], // currency = price denomination\n allowed_assets: [ // (chain, token) pairs \u2014 the buyer picks one; mix tokens freely\n { chain: 'base', token: 'USDC' },\n { chain: 'arbitrum', token: 'USDC' },\n { chain: 'tron', token: 'USDT' },\n { chain: 'solana', token: 'USDC' },\n ],\n // shorthand for one token across chains: allowed_pay_chains: ['base','arbitrum','solana']\n});\n```\n\n> **Payment options** `allowed_assets` (chain\xD7token pairs) is the standard way \u2014 the buyer picks any listed pair. `chain` takes a short-name (`'base'`, `'tron'`, `'solana'`) or a CAIP-2 id and resolves to your key's mode (test/live). Shorthands: `allowed_pay_chains` (one token across chains) and `pay_chain` (single fixed chain). `allowed_assets` takes precedence.\n\n## Example: poll status\n\n```\nconst { data } = await bitpal.checkout.getStatus(sessionId);\nif (data.status === 'paid_confirmed' || data.status === 'swept') showSuccess(); // UI only \u2014 fulfil from the webhook\n```\n\nStatus values: `created` \u2192 `awaiting_deposit` \u2192 `deposit_detected` \u2192 `paid_confirmed` \u2192 `swept`, plus `expired`, `unresolved_underpaid`, `wrong_token`. (Sessions opened through the legacy wallet-connect flow may report `pending_payment`; treat it like `created`.) See [Payment lifecycle](/docs/md/lifecycle.md).\n\n## Method reference\n\n| Method | Purpose |\n| --- | --- |\n| `createSession(params)` | Create a session \u2192 `checkout_url`. |\n| `getSession(id)` | Full session (authenticated). |\n| `getPublicSession(id)` | Buyer-facing: payment options, status. |\n| `getStatus(id)` | Lightweight status for polling. |\n| `previewFee(amount)` | Fee preview before charging. |\n| `products` / `prices` | Catalog: reusable products & prices (for payment links). |\n| `paymentLinks` | Create & manage shareable payment links from a price. |\n\nAmount helpers (advanced, for `amount_atomic`): `toAtomic(v, decimals)` / `fromAtomic(v, decimals)`."
|
|
4127
4127
|
},
|
|
4128
4128
|
{
|
|
4129
4129
|
slug: "webhooks",
|
|
4130
4130
|
title: "Webhooks",
|
|
4131
4131
|
url: "https://bitpal.io/docs/#webhooks",
|
|
4132
4132
|
markdownUrl: "https://bitpal.io/docs/md/webhooks.md",
|
|
4133
|
-
markdown: '# Webhooks\n\nSigned JSON events as a session moves through its lifecycle. Delivered at least once \u2014 keep handlers idempotent.\n\n## Events\n\n| Event | Fires when |\n| --- | --- |\n| `checkout.session.created` | A session is created. |\n| `checkout.session.deposit_detected` | Funds seen on-chain, awaiting confirmations. |\n| `checkout.session.paid` | Confirmed \u2014 safe to fulfil. `late: true` if it arrived after the deadline. |\n| `checkout.session.settled` | Funds swept to your payout wallet. |\n| `checkout.session.unresolved` | Underpaid, wrong token, or a confirmed deposit reverted by a reorg (`reason`). |\n| `checkout.session.expired` | Deadline passed without full payment. |\n| `checkout.refund.recorded` | A refund was made on-chain (`kind`). |\n| `x402.payment.resolved` | An x402 call that settled with `503` reached its outcome. **Only fires for those** \u2014 the normal path answers inline. |\n\n## Verify & handle\n\nEach request carries `X-Webhook-Signature-256` (`sha256=<hex>`) and `X-Webhook-Timestamp`. Verify both, against the **raw bytes**.\n\n```\nimport express from \'express\';\nimport { verifyWebhookSignature, parseWebhookEvent, WEBHOOK_HEADERS } from \'@bitpal/checkout\';\n\napp.post(\'/webhooks/bitpal\', express.raw({ type: \'application/json\' }), async (req, res) => {\n const raw = req.body.toString(\'utf8\'); // exact bytes, not re-serialized JSON\n const ok = await verifyWebhookSignature( // returns a Promise \u2014 always await it\n raw,\n req.header(WEBHOOK_HEADERS.signature) ?? \'\',\n process.env.BITPAL_WEBHOOK_SECRET,\n { timestamp: req.header(WEBHOOK_HEADERS.timestamp) ?? \'\' }, // replay defense \u2014 required\n );\n if (!ok) return res.status(400).send(\'invalid signature\');\n const event = parseWebhookEvent(raw);\n if (event.event === \'checkout.session.paid\') await fulfil(event.data.session_id); // idempotent, dedup on event.id\n res.sendStatus(200);\n});\n```\n\n> **Verify against the raw body, and await the result.** Re-serializing the JSON changes the bytes and breaks the signature \u2014 capture the raw body before JSON middleware. `verifyWebhookSignature` is async: without `await` the check is always truthy and forged requests pass.\n\n## Request format\n\nEvery delivery is an HTTP `POST` with a JSON body:\n\n```\n{\n "id": "1ff63374-9417-4f87-850d-e869a3bd2dbe", // event id \u2014 dedup on this\n "event": "checkout.session.paid",\n "created_at": "2026-11-06T07:20:49.549Z", // when the event happened (unchanged on retries)\n "data": { "session_id": "cs_\u2026", \u2026 }\n}\n```\n\n| Header | Value |\n| --- | --- |\n| `X-Webhook-Signature-256` | `sha256=<hex>` \u2014 HMAC-SHA256 of `<X-Webhook-Timestamp>.<raw body>` |\n| `X-Webhook-Timestamp` | ISO-8601 UTC time of **this attempt** (new on every retry) |\n| `X-Webhook-Event` | Event name (unsigned \u2014 read it from the body) |\n| `X-Webhook-Id` | Event id (unsigned \u2014 dedup on the body `id`) |\n| `X-Webhook-Delivery-Id` | Id of this delivery, for support requests |\n\n## Verifying the signature\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Reject the request if `X-Webhook-Timestamp` is more than 5 minutes from your clock (replay protection).\n3. Compute `HMAC-SHA256(key = your whsec_\u2026 secret, message = timestamp + "." + raw body)` as lowercase hex.\n4. Compare it with the part after `sha256=` using a constant-time comparison.\n\n### Python\n\n```\nimport hmac, hashlib, time\nfrom datetime import datetime\n\ndef verify_bitpal_webhook(raw_body: bytes, signature: str, timestamp: str, secret: str, tolerance_sec: int = 300) -> bool:\n if not signature.startswith("sha256=") or not timestamp:\n return False\n try:\n sent = datetime.fromisoformat(timestamp.replace("Z", "+00:00")).timestamp()\n except ValueError:\n return False\n if abs(time.time() - sent) > tolerance_sec:\n return False\n expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()\n return hmac.compare_digest(signature[len("sha256="):], expected)\n\n# Flask: verify_bitpal_webhook(request.get_data(), request.headers.get("X-Webhook-Signature-256", ""),\n# request.headers.get("X-Webhook-Timestamp", ""), os.environ["BITPAL_WEBHOOK_SECRET"])\n```\n\n### Go\n\n```\nimport (\n "crypto/hmac"\n "crypto/sha256"\n "encoding/hex"\n "strings"\n "time"\n)\n\nfunc VerifyBitPalWebhook(rawBody []byte, signature, timestamp, secret string) bool {\n const prefix = "sha256="\n if !strings.HasPrefix(signature, prefix) || timestamp == "" {\n return false\n }\n sent, err := time.Parse(time.RFC3339Nano, timestamp)\n if err != nil {\n return false\n }\n if d := time.Since(sent); d > 5*time.Minute || d < -5*time.Minute {\n return false\n }\n mac := hmac.New(sha256.New, []byte(secret))\n mac.Write([]byte(timestamp + "."))\n mac.Write(rawBody)\n expected := hex.EncodeToString(mac.Sum(nil))\n return hmac.Equal([]byte(signature[len(prefix):]), []byte(expected))\n}\n```\n\n### PHP\n\n```\nfunction verify_bitpal_webhook(string $rawBody, string $signature, string $timestamp, string $secret, int $toleranceSec = 300): bool {\n if (strpos($signature, \'sha256=\') !== 0 || $timestamp === \'\') return false;\n $sent = strtotime($timestamp);\n if ($sent === false || abs(time() - $sent) > $toleranceSec) return false;\n $expected = hash_hmac(\'sha256\', $timestamp . \'.\' . $rawBody, $secret);\n return hash_equals($expected, substr($signature, 7));\n}\n\n$ok = verify_bitpal_webhook(\n file_get_contents(\'php://input\'),\n $_SERVER[\'HTTP_X_WEBHOOK_SIGNATURE_256\'] ?? \'\',\n $_SERVER[\'HTTP_X_WEBHOOK_TIMESTAMP\'] ?? \'\',\n getenv(\'BITPAL_WEBHOOK_SECRET\')\n);\n```\n\n### Java\n\n```\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\nimport java.time.DateTimeException;\nimport java.time.Duration;\nimport java.time.Instant;\n\npublic static boolean verifyBitPalWebhook(byte[] rawBody, String signature, String timestamp, String secret) throws Exception {\n if (signature == null || !signature.startsWith("sha256=") || timestamp == null || timestamp.isEmpty()) return false;\n Instant sent;\n try { sent = Instant.parse(timestamp); } catch (DateTimeException e) { return false; }\n if (Duration.between(sent, Instant.now()).abs().getSeconds() > 300) return false;\n Mac mac = Mac.getInstance("HmacSHA256");\n mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));\n mac.update((timestamp + ".").getBytes(StandardCharsets.UTF_8));\n byte[] digest = mac.doFinal(rawBody);\n StringBuilder hex = new StringBuilder();\n for (byte b : digest) hex.append(String.format("%02x", b));\n return MessageDigest.isEqual(hex.toString().getBytes(StandardCharsets.UTF_8),\n signature.substring(7).getBytes(StandardCharsets.UTF_8));\n}\n```\n\n## Event data\n\nEvery `data` object has `session_id`. Session events also carry `amount_total` (6-decimal integer string), `currency` and `late`. Token amounts are in the pay token\'s decimals \u2014 see [Payment lifecycle \u2192 Amounts](/docs/md/lifecycle.md).\n\n| Event | Extra fields in `data` |\n| --- | --- |\n| `checkout.session.created` | \u2014 (only `session_id`, `amount_total`, `currency`) |\n| `checkout.session.deposit_detected` | `tx_hash`, `amount_received`, `received_token` (token contract) |\n| `checkout.session.paid` | `tx_hash`, `late` |\n| `checkout.session.settled` | `sweep_tx_hash` |\n| `checkout.session.unresolved` | `reason`: `underpaid` (+ `amount_received`, `amount_expected`, `received_token`, `tx_hash`) \xB7 `wrong_token` (+ `received_token`) \xB7 `reorg` |\n| `checkout.session.expired` | `reason: "no_deposit"`, or `refund_tx_hash` when a partial payment was refunded |\n| `checkout.refund.recorded` | `kind` (`overpay_excess` \xB7 `underpay_expired` \xB7 `wrong_token` \xB7 `late_deposit`), `amount`, `token`, `tx_hash`, `refund_address` |\n\n### Example: `checkout.session.paid`\n\n```\n{\n "id": "1ff63374-9417-4f87-850d-e869a3bd2dbe",\n "event": "checkout.session.paid",\n "created_at": "2026-11-06T07:20:49.549Z",\n "data": {\n "session_id": "cs_HFoj91aFFbHSRk1LN1-9K01q",\n "amount_total": "1000000",\n "currency": "USDC",\n "late": false,\n "tx_hash": "0xd9a75dfe753b20027a0a93841c4e70af1b6be061b59e530d1bc381e88c196fe3"\n }\n}\n```\n\n### Example: `checkout.session.unresolved` (underpaid)\n\n```\n{\n "id": "18d80275-45be-43d7-a57b-4b5038bd2736",\n "event": "checkout.session.unresolved",\n "created_at": "2026-11-06T07:14:19.252Z",\n "data": {\n "session_id": "cs_1ATJ7V5QknzeFpTNPMU_449s",\n "amount_total": "5000000",\n "currency": "USDC",\n "late": false,\n "reason": "underpaid",\n "amount_expected": "5000000",\n "amount_received": "2500000",\n "received_token": "0x943b94Dacf1507F2658f1CfE103Ac36Ebc13Ef65",\n "tx_hash": "0x4461291b5d743d3950a474c015098b8cb53b4373346bd510f74481dd8dbb9d65"\n }\n}\n```\n\n### Example: `checkout.refund.recorded`\n\n```\n{\n "id": "f2fdcce6-1880-44b2-8351-906c42b7c4cd",\n "event": "checkout.refund.recorded",\n "created_at": "2026-11-06T07:20:29.547Z",\n "data": {\n "session_id": "cs_YsrZGcZzVsVHJo8ZNVmsTL1D",\n "kind": "overpay_excess",\n "amount": "1000000",\n "token": "0x943b94Dacf1507F2658f1CfE103Ac36Ebc13Ef65",\n "tx_hash": "0xe943412e2be8b3e3fc6359b47a8eea0cc4de17768b069f52ebab1aacd873aec4",\n "refund_address": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"\n }\n}\n```\n\n## Delivery, retries and ordering\n\n- Respond with any **2xx within 10 seconds**. Do slow work after responding (queue it).\n- Failed deliveries are retried with exponential backoff. After 5 failed attempts the delivery moves to the **dead-letter queue**; resend it from Console \u2192 Developers \u2192 Webhooks once your endpoint is fixed.\n- Delivery is **at least once**: the same event can arrive more than once. Deduplicate on the body `id` and store it in the same transaction as your side effect.\n- Order is not guaranteed \u2014 a retried `paid` can arrive after `settled`. Keep the furthest state you have seen instead of overwriting it with an older one.\n- Test and live are separate: each environment has its own endpoints and secrets.\n\n## Testing\n\n- **Send Test** (Console \u2192 Developers \u2192 Webhooks) posts a signed sample event to your endpoint.\n- For local development, expose your server with a tunnel (ngrok, cloudflared) and register the tunnel URL in test mode.\n- Run a real test payment ([Quickstart](/docs/md/quickstart.md)) to receive the full sequence of events.'
|
|
4133
|
+
markdown: '# Webhooks\n\nSigned JSON events as a session moves through its lifecycle. Delivered at least once \u2014 keep handlers idempotent.\n\n## Events\n\n| Event | Fires when |\n| --- | --- |\n| `checkout.session.created` | A session is created. |\n| `checkout.session.deposit_detected` | Funds seen on-chain, awaiting confirmations. |\n| `checkout.session.paid` | Confirmed \u2014 safe to fulfil. `late: true` if it arrived after the deadline. |\n| `checkout.session.settled` | Funds swept to your payout wallet. |\n| `checkout.session.unresolved` | Underpaid, wrong token, or a confirmed deposit reverted by a reorg (`reason`). |\n| `checkout.session.expired` | Deadline passed without full payment. |\n| `checkout.refund.recorded` | A refund was made on-chain (`kind`). |\n| `x402.payment.resolved` | An x402 call that settled with `503` reached its outcome. **Only fires for those** \u2014 the normal path answers inline. |\n\n## Verify & handle\n\nEach request carries `X-Webhook-Signature-256` (`sha256=<hex>`) and `X-Webhook-Timestamp`. Verify both, against the **raw bytes**.\n\n```\nimport express from \'express\';\nimport { verifyWebhookSignature, parseWebhookEvent, WEBHOOK_HEADERS } from \'@bitpal/checkout\';\n\napp.post(\'/webhooks/bitpal\', express.raw({ type: \'application/json\' }), async (req, res) => {\n const raw = req.body.toString(\'utf8\'); // exact bytes, not re-serialized JSON\n const ok = await verifyWebhookSignature( // returns a Promise \u2014 always await it\n raw,\n req.header(WEBHOOK_HEADERS.signature) ?? \'\',\n process.env.BITPAL_WEBHOOK_SECRET,\n { timestamp: req.header(WEBHOOK_HEADERS.timestamp) ?? \'\' }, // replay defense \u2014 required\n );\n if (!ok) return res.status(400).send(\'invalid signature\');\n const event = parseWebhookEvent(raw);\n if (event.event === \'checkout.session.paid\') await fulfil(event.data.session_id); // idempotent, dedup on event.id\n res.sendStatus(200);\n});\n```\n\n> **Verify against the raw body, and await the result.** Re-serializing the JSON changes the bytes and breaks the signature \u2014 capture the raw body before JSON middleware. `verifyWebhookSignature` is async: without `await` the check is always truthy and forged requests pass.\n\n## Request format\n\nEvery delivery is an HTTP `POST` with a JSON body:\n\n```\n{\n "id": "1ff63374-9417-4f87-850d-e869a3bd2dbe", // event id \u2014 dedup on this\n "event": "checkout.session.paid",\n "created_at": "2026-11-06T07:20:49.549Z", // when the event happened (unchanged on retries)\n "data": { "session_id": "cs_\u2026", \u2026 }\n}\n```\n\n| Header | Value |\n| --- | --- |\n| `X-Webhook-Signature-256` | `sha256=<hex>` \u2014 HMAC-SHA256 of `<X-Webhook-Timestamp>.<raw body>` |\n| `X-Webhook-Timestamp` | ISO-8601 UTC time of **this attempt** (new on every retry) |\n| `X-Webhook-Event` | Event name (unsigned \u2014 read it from the body) |\n| `X-Webhook-Id` | Event id (unsigned \u2014 dedup on the body `id`) |\n| `X-Webhook-Delivery-Id` | Id of this delivery, for support requests |\n\n## Verifying the signature\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Reject the request if `X-Webhook-Timestamp` is more than 5 minutes from your clock (replay protection).\n3. Compute `HMAC-SHA256(key = your whsec_\u2026 secret, message = timestamp + "." + raw body)` as lowercase hex.\n4. Compare it with the part after `sha256=` using a constant-time comparison.\n\n### Python\n\n```\nimport hmac, hashlib, time\nfrom datetime import datetime\n\ndef verify_bitpal_webhook(raw_body: bytes, signature: str, timestamp: str, secret: str, tolerance_sec: int = 300) -> bool:\n if not signature.startswith("sha256=") or not timestamp:\n return False\n try:\n sent = datetime.fromisoformat(timestamp.replace("Z", "+00:00")).timestamp()\n except (ValueError, OverflowError, OSError):\n return False\n if abs(time.time() - sent) > tolerance_sec:\n return False\n expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()\n # compare bytes: compare_digest raises TypeError on non-ASCII str input\n return hmac.compare_digest(signature[len("sha256="):].encode(), expected.encode())\n\n# Flask: verify_bitpal_webhook(request.get_data(), request.headers.get("X-Webhook-Signature-256", ""),\n# request.headers.get("X-Webhook-Timestamp", ""), os.environ["BITPAL_WEBHOOK_SECRET"])\n```\n\n### Go\n\n```\nimport (\n "crypto/hmac"\n "crypto/sha256"\n "encoding/hex"\n "strings"\n "time"\n)\n\nfunc VerifyBitPalWebhook(rawBody []byte, signature, timestamp, secret string) bool {\n const prefix = "sha256="\n if !strings.HasPrefix(signature, prefix) || timestamp == "" {\n return false\n }\n sent, err := time.Parse(time.RFC3339Nano, timestamp)\n if err != nil {\n return false\n }\n if d := time.Since(sent); d > 5*time.Minute || d < -5*time.Minute {\n return false\n }\n mac := hmac.New(sha256.New, []byte(secret))\n mac.Write([]byte(timestamp + "."))\n mac.Write(rawBody)\n expected := hex.EncodeToString(mac.Sum(nil))\n return hmac.Equal([]byte(signature[len(prefix):]), []byte(expected))\n}\n```\n\n### PHP\n\n```\nfunction verify_bitpal_webhook(string $rawBody, string $signature, string $timestamp, string $secret, int $toleranceSec = 300): bool {\n if (strpos($signature, \'sha256=\') !== 0 || $timestamp === \'\') return false;\n $sent = strtotime($timestamp);\n if ($sent === false || abs(time() - $sent) > $toleranceSec) return false;\n $expected = hash_hmac(\'sha256\', $timestamp . \'.\' . $rawBody, $secret);\n return hash_equals($expected, substr($signature, 7));\n}\n\n$secret = getenv(\'BITPAL_WEBHOOK_SECRET\'); // false when unset\nif ($secret === false || $secret === \'\') {\n http_response_code(500); // misconfigured \u2014 never verify against an empty key\n exit;\n}\n$ok = verify_bitpal_webhook(\n (string) file_get_contents(\'php://input\'),\n $_SERVER[\'HTTP_X_WEBHOOK_SIGNATURE_256\'] ?? \'\',\n $_SERVER[\'HTTP_X_WEBHOOK_TIMESTAMP\'] ?? \'\',\n $secret\n);\n```\n\n### Java\n\n```\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\nimport java.time.DateTimeException;\nimport java.time.Duration;\nimport java.time.Instant;\n\npublic static boolean verifyBitPalWebhook(byte[] rawBody, String signature, String timestamp, String secret) throws Exception {\n if (signature == null || !signature.startsWith("sha256=") || timestamp == null || timestamp.isEmpty()) return false;\n Instant sent;\n try { sent = Instant.parse(timestamp); } catch (DateTimeException e) { return false; }\n if (Duration.between(sent, Instant.now()).abs().getSeconds() > 300) return false;\n Mac mac = Mac.getInstance("HmacSHA256");\n mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));\n mac.update((timestamp + ".").getBytes(StandardCharsets.UTF_8));\n byte[] digest = mac.doFinal(rawBody);\n StringBuilder hex = new StringBuilder();\n for (byte b : digest) hex.append(String.format("%02x", b));\n return MessageDigest.isEqual(hex.toString().getBytes(StandardCharsets.UTF_8),\n signature.substring(7).getBytes(StandardCharsets.UTF_8));\n}\n```\n\n## Event data\n\nEvery `data` object has `session_id`. Session events also carry `amount_total` (6-decimal integer string), `currency` and `late`. Token amounts are in the pay token\'s decimals \u2014 see [Payment lifecycle \u2192 Amounts](/docs/md/lifecycle.md).\n\n| Event | Extra fields in `data` |\n| --- | --- |\n| `checkout.session.created` | \u2014 (only `session_id`, `amount_total`, `currency`) |\n| `checkout.session.deposit_detected` | `tx_hash`, `amount_received`, `received_token` (token contract) |\n| `checkout.session.paid` | `tx_hash`, `late` |\n| `checkout.session.settled` | `sweep_tx_hash` |\n| `checkout.session.unresolved` | `reason`: `underpaid` (+ `amount_received`, `amount_expected`, `received_token`, `tx_hash`) \xB7 `wrong_token` (+ `received_token`) \xB7 `reorg` |\n| `checkout.session.expired` | `reason: "no_deposit"`, or `refund_tx_hash` when a partial payment was refunded |\n| `checkout.refund.recorded` | `kind` (`overpay_excess` \xB7 `underpay_expired` \xB7 `wrong_token` \xB7 `late_deposit`), `amount`, `token`, `tx_hash`, `refund_address` |\n\n### Example: `checkout.session.paid`\n\n```\n{\n "id": "1ff63374-9417-4f87-850d-e869a3bd2dbe",\n "event": "checkout.session.paid",\n "created_at": "2026-11-06T07:20:49.549Z",\n "data": {\n "session_id": "cs_HFoj91aFFbHSRk1LN1-9K01q",\n "amount_total": "1000000",\n "currency": "USDC",\n "late": false,\n "tx_hash": "0xd9a75dfe753b20027a0a93841c4e70af1b6be061b59e530d1bc381e88c196fe3"\n }\n}\n```\n\n### Example: `checkout.session.unresolved` (underpaid)\n\n```\n{\n "id": "18d80275-45be-43d7-a57b-4b5038bd2736",\n "event": "checkout.session.unresolved",\n "created_at": "2026-11-06T07:14:19.252Z",\n "data": {\n "session_id": "cs_1ATJ7V5QknzeFpTNPMU_449s",\n "amount_total": "5000000",\n "currency": "USDC",\n "late": false,\n "reason": "underpaid",\n "amount_expected": "5000000",\n "amount_received": "2500000",\n "received_token": "0x943b94Dacf1507F2658f1CfE103Ac36Ebc13Ef65",\n "tx_hash": "0x4461291b5d743d3950a474c015098b8cb53b4373346bd510f74481dd8dbb9d65"\n }\n}\n```\n\n### Example: `checkout.refund.recorded`\n\n```\n{\n "id": "f2fdcce6-1880-44b2-8351-906c42b7c4cd",\n "event": "checkout.refund.recorded",\n "created_at": "2026-11-06T07:20:29.547Z",\n "data": {\n "session_id": "cs_YsrZGcZzVsVHJo8ZNVmsTL1D",\n "kind": "overpay_excess",\n "amount": "1000000",\n "token": "0x943b94Dacf1507F2658f1CfE103Ac36Ebc13Ef65",\n "tx_hash": "0xe943412e2be8b3e3fc6359b47a8eea0cc4de17768b069f52ebab1aacd873aec4",\n "refund_address": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"\n }\n}\n```\n\n## Delivery, retries and ordering\n\n- Respond with any **2xx within 10 seconds**. Do slow work after responding (queue it).\n- A failed delivery (non-2xx, timeout or connection error) is retried right away twice (after 1 s and 2 s), then after **1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h** \u2014 10 attempts over about a day. After the last one the delivery moves to the **dead-letter queue**; resend it from Console \u2192 Developers \u2192 Webhooks once your endpoint is fixed (dead-lettered deliveries can be resent for 30 days).\n- Delivery is **at least once**: the same event can arrive more than once. Deduplicate on the body `id` and store it in the same transaction as your side effect.\n- Order is not guaranteed \u2014 a retried `paid` can arrive after `settled`. Keep the furthest state you have seen instead of overwriting it with an older one. Not every event is sent for every payment: `paid` can arrive with no `deposit_detected` before it (a late payment, or a deposit already confirmed when it was first seen).\n- Test and live are separate: each environment has its own endpoints and secrets.\n\n## Testing\n\n- **Send Test** (Console \u2192 Developers \u2192 Webhooks) posts a signed sample event to your endpoint.\n- For local development, expose your server with a tunnel (ngrok, cloudflared) and register the tunnel URL in test mode.\n- Run a real test payment ([Quickstart](/docs/md/quickstart.md)) to receive the full sequence of events.'
|
|
4134
4134
|
},
|
|
4135
4135
|
{
|
|
4136
4136
|
slug: "x402",
|
|
4137
4137
|
title: "x402",
|
|
4138
4138
|
url: "https://bitpal.io/docs/#x402",
|
|
4139
4139
|
markdownUrl: "https://bitpal.io/docs/md/x402.md",
|
|
4140
|
-
markdown: '# x402\n\nCharge per API call. The caller pays with a signed authorization and retries \u2014 no checkout page, no redirect.\n\n## How it works\n\n1. An unpaid request to your route returns `402` with the payment terms.\n2. The caller signs an EIP-3009 authorization (gasless \u2014 signature only) and retries with an `X-PAYMENT` header.\n3. BitPal verifies the signature and broadcasts it. Funds move from the payer through a per-terms settlement address that pays you and the fee wallet in the same transaction \u2014 BitPal never holds them.\n4. Your handler runs and the response carries `X-PAYMENT-RESPONSE` with the transaction hash.\n\n> Payments settle to the **same payout wallet as checkout**. Register one in the console first \u2014 x402 has no separate wallet setting.\n\n## Add it to a route\n\n```\nnpm install @bitpal/checkout\n```\n\n```\nimport { x402 } from \'@bitpal/checkout\';\n\n// $1.00 USDC per call\napp.get(\'/report\', x402({\n apiKey: process.env.BITPAL_API_KEY, // bp_test_\u2026 or bp_live_\u2026\n payTo: \'0xYourPayoutWallet\'
|
|
4140
|
+
markdown: '# x402\n\nCharge per API call. The caller pays with a signed authorization and retries \u2014 no checkout page, no redirect.\n\n## How it works\n\n1. An unpaid request to your route returns `402` with the payment terms.\n2. The caller signs an EIP-3009 authorization (gasless \u2014 signature only) and retries with an `X-PAYMENT` header.\n3. BitPal verifies the signature and broadcasts it. Funds move from the payer through a per-terms settlement address that pays you and the fee wallet in the same transaction \u2014 BitPal never holds them.\n4. Your handler runs and the response carries `X-PAYMENT-RESPONSE` with the transaction hash.\n\n> Payments settle to the **same payout wallet as checkout**. Register one in the console first \u2014 x402 has no separate wallet setting.\n\n## Add it to a route\n\n```\nnpm install @bitpal/checkout\n```\n\n```\nimport { x402 } from \'@bitpal/checkout\';\n\n// $1.00 USDC per call\napp.get(\'/report\', x402({\n apiKey: process.env.BITPAL_API_KEY, // bp_test_\u2026 or bp_live_\u2026\n payTo: \'0xYourPayoutWallet\', // the payout wallet registered in Console for this mode \u2014 anything else: PAYTO_NOT_MERCHANT_WALLET\n network: \'base\', // test key \u2192 \'base-sepolia\'\n amount: \'1.00\', // \uC0AC\uB78C \uD45C\uAE30 \u2014 decimals \uACC4\uC0B0 \uBD88\uD544\uC694\n}), (req, res) => {\n res.json({ report: \'\u2026\' });\n});\n```\n\n## Other runtimes \u2014 Hono, Fastify, Next.js\n\nThe `x402()` adapter above is Express-shaped. Everywhere else, use `X402Gate` directly \u2014 it takes the incoming header and hands back what to send. Same logic, no framework assumptions.\n\n```\nimport { X402Gate, createMemoryReplayStore, X402_PAYMENT_RESPONSE_HEADER } from \'@bitpal/checkout\';\n\n// \uBC18\uB4DC\uC2DC \uBAA8\uB4C8 \uC2A4\uCF54\uD504\uC5D0 \uD558\uB098 \u2014 \uC544\uB798 \uACBD\uACE0 \uCC38\uC870\nconst replayStore = createMemoryReplayStore();\n\nconst gate = new X402Gate({\n apiKey: process.env.BITPAL_API_KEY,\n payTo: \'0xYourPayoutWallet\',\n network: \'base\',\n amount: \'1.00\',\n replayStore,\n});\n\n// Hono \uC608\uC2DC \u2014 Next.js Route Handler \uB3C4 \uBAA8\uC591\uC774 \uAC19\uB2E4\napp.get(\'/report\', async (c) => {\n const url = new URL(c.req.url);\n // \uACB0\uC81C\uB294 \uC774 \uBB38\uC790\uC5F4\uC5D0 \uBB36\uC778\uB2E4. 402 \uC640 \uC7AC\uC694\uCCAD\uC5D0\uC11C \uAC19\uC740 \uAC12\uC774\uC5B4\uC57C \uD55C\uB2E4(\uCFFC\uB9AC \uC81C\uC678).\n const resource = `${url.origin}${url.pathname}`;\n\n const result = await gate.collect({ header: c.req.header(\'x-payment\'), resource });\n if (result.kind !== \'settled\') {\n // 402(\uBBF8\uACB0\uC81C) \xB7 409(\uC7AC\uC0AC\uC6A9) \xB7 503(\uBBF8\uD655\uC815) \uC744 \uADF8\uB300\uB85C \uC62E\uAE34\uB2E4\n return c.json(result.body, result.status);\n }\n\n c.header(X402_PAYMENT_RESPONSE_HEADER, result.paymentResponseHeader);\n return c.json({ report: \'\u2026\' }); // \uACB0\uC81C\uB41C \uC694\uCCAD\uB9CC \uC5EC\uAE30 \uB3C4\uB2EC\n});\n```\n\n> **Create the replay store once, at module scope.** The gate uses it to remember which authorizations have already been served \u2014 that is what makes one payment unlock exactly one response. Build it inside the handler and every request gets a fresh, empty memory, so the same `X-PAYMENT` can be replayed for more calls. (Your funds are never at risk \u2014 BitPal settles a given authorization once and the on-chain nonce is spent \u2014 but you would be giving the resource away.) Running more than one instance? Back it with a shared store: `X402ReplayStore` is an interface, so Redis or a table works.\n\n## The 402 response\n\nThis is **standard x402** \u2014 a stock client such as `x402-fetch` can pay it. `payTo` is **not your wallet**: it is a CREATE2 address derived from the payment terms, which is what lets the caller sign a single authorization and still have the fee split correctly. `feeBreakdown` is a BitPal extension and is informational \u2014 a caller may ignore it and still pay correctly.\n\n> **Do not hand-roll this body.** `payTo` is computed on-chain from the terms, so only BitPal can produce a valid one. Without the middleware, call `POST /v1/x402/requirements` and return what it gives you \u2014 then `/verify` and `/settle` with the caller\'s `X-PAYMENT`.\n\n```\n{\n "x402Version": 1,\n "accepts": [{\n "scheme": "exact",\n "network": "base",\n "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",\n // \uC815\uC0B0 \uC9C0\uAC11\uC774 \uC544\uB2C8\uB77C \uC870\uAC74\uC5D0\uC11C \uC720\uB3C4\uB41C \uC815\uC0B0 \uC8FC\uC18C (CREATE2)\n "payTo": "0xSettlementAddressDerivedFromTerms",\n // gross \u2014 \uC218\uC218\uB8CC \uCC28\uAC10 \uC804 \uCD1D\uC561. \uD638\uCD9C\uC790\uB294 \uC774 \uAE08\uC561\uC744 payTo \uB85C \uC11C\uBA85\uD55C\uB2E4.\n "maxAmountRequired": "1000000",\n "resource": "https://your.api/report",\n "extra": { "name": "USD Coin", "version": "2", "chainId": 8453 },\n "feeBreakdown": {\n "netAmount": "985000",\n "feeAmount": "15000",\n "feeBps": 150,\n "feeRecipient": "0x\u2026"\n }\n }]\n}\n```\n\n## Rules worth knowing\n\n- **Exact amounts only.** Over- and underpayment are both rejected \u2014 there is no refund path on this lane.\n- **One payment, one response.** A given authorization unlocks a single response; replaying it does not buy another call.\n- **Bound to the resource.** A payment verified for one route cannot be settled against another.\n- **EOA signers only.** Smart-contract wallets cannot sign EIP-3009 authorizations.\n- **Standard x402 payload.** The caller signs one authorization \u2014 any x402 client works, including ones that have never heard of BitPal.\n- If `settle` times out, poll the payment status instead of retrying blindly \u2014 the call is idempotent either way.\n\n## When settle is uncertain\n\nIf the chain is slow or the broadcast result is unknown, the gate returns `503` instead of your resource. **Do not retry blindly** \u2014 the payment may well have gone through. You have two ways to learn the outcome, and they carry the same answer:\n\n- **Poll** \u2014 `GET /v1/x402/payments/:id` with the `paymentId` from the 503 body.\n- **Webhook** \u2014 `x402.payment.resolved` is pushed once BitPal settles the question, so you do not have to sit on a polling loop.\n\n> Record the `paymentId` when you get a 503. It is how you tie the later webhook back to the caller who is waiting \u2014 and how you answer them if they ask.\n\n## Supported\n\n| Network | Token | `network` |\n| --- | --- | --- |\n| Base | USDC | `base` |\n| Base Sepolia | USDC | `base-sepolia` |\n\nTest keys accept only `base-sepolia`; live keys only `base`. More chains follow the same flow as they are enabled \u2014 see [Chains & contracts](/docs/md/reference.md).'
|
|
4141
4141
|
}
|
|
4142
4142
|
],
|
|
4143
4143
|
sections: [
|
|
@@ -4160,28 +4160,63 @@ var docs_data_default = {
|
|
|
4160
4160
|
pageTitle: "Use with AI",
|
|
4161
4161
|
heading: "Setup",
|
|
4162
4162
|
url: "https://bitpal.io/docs/#ai",
|
|
4163
|
-
text:
|
|
4163
|
+
text: "## Setup\n\nReplace `bp_test_\u2026` with your test key (Console \u2192 Developers \u2192 API Keys), or leave the key out for docs only."
|
|
4164
4164
|
},
|
|
4165
4165
|
{
|
|
4166
4166
|
page: "ai",
|
|
4167
4167
|
pageTitle: "Use with AI",
|
|
4168
|
-
heading: "
|
|
4168
|
+
heading: "Setup \u203A Claude Code",
|
|
4169
4169
|
url: "https://bitpal.io/docs/#ai",
|
|
4170
|
-
text:
|
|
4170
|
+
text: '### Claude Code\n\n```\n# remote\nclaude mcp add --transport http bitpal https://mcp.bitpal.io/mcp --header "Authorization: Bearer bp_test_\u2026"\n# local\nclaude mcp add bitpal -e BITPAL_API_KEY=bp_test_\u2026 -- npx -y @bitpal/mcp\n```'
|
|
4171
4171
|
},
|
|
4172
4172
|
{
|
|
4173
4173
|
page: "ai",
|
|
4174
4174
|
pageTitle: "Use with AI",
|
|
4175
|
-
heading: "
|
|
4175
|
+
heading: "Setup \u203A Cursor \u2014 `~/.cursor/mcp.json`",
|
|
4176
4176
|
url: "https://bitpal.io/docs/#ai",
|
|
4177
|
-
text:
|
|
4177
|
+
text: '### Cursor \u2014 `~/.cursor/mcp.json`\n\n```\n{\n "mcpServers": {\n "bitpal": { "url": "https://mcp.bitpal.io/mcp", "headers": { "Authorization": "Bearer bp_test_\u2026" } }\n }\n}\n```'
|
|
4178
4178
|
},
|
|
4179
4179
|
{
|
|
4180
4180
|
page: "ai",
|
|
4181
4181
|
pageTitle: "Use with AI",
|
|
4182
|
-
heading: "
|
|
4182
|
+
heading: "Setup \u203A VS Code \u2014 `.vscode/mcp.json`",
|
|
4183
4183
|
url: "https://bitpal.io/docs/#ai",
|
|
4184
|
-
text: '
|
|
4184
|
+
text: '### VS Code \u2014 `.vscode/mcp.json`\n\n```\n{\n "servers": {\n "bitpal": { "type": "http", "url": "https://mcp.bitpal.io/mcp", "headers": { "Authorization": "Bearer bp_test_\u2026" } }\n }\n}\n```'
|
|
4185
|
+
},
|
|
4186
|
+
{
|
|
4187
|
+
page: "ai",
|
|
4188
|
+
pageTitle: "Use with AI",
|
|
4189
|
+
heading: "Setup \u203A Windsurf \u2014 `~/.codeium/windsurf/mcp_config.json`",
|
|
4190
|
+
url: "https://bitpal.io/docs/#ai",
|
|
4191
|
+
text: '### Windsurf \u2014 `~/.codeium/windsurf/mcp_config.json`\n\n```\n{\n "mcpServers": {\n "bitpal": { "serverUrl": "https://mcp.bitpal.io/mcp", "headers": { "Authorization": "Bearer bp_test_\u2026" } }\n }\n}\n```'
|
|
4192
|
+
},
|
|
4193
|
+
{
|
|
4194
|
+
page: "ai",
|
|
4195
|
+
pageTitle: "Use with AI",
|
|
4196
|
+
heading: "Setup \u203A Claude Desktop \u2014 `claude_desktop_config.json`",
|
|
4197
|
+
url: "https://bitpal.io/docs/#ai",
|
|
4198
|
+
text: '### Claude Desktop \u2014 `claude_desktop_config.json`\n\n```\n{\n "mcpServers": {\n "bitpal": { "command": "npx", "args": ["-y", "@bitpal/mcp"], "env": { "BITPAL_API_KEY": "bp_test_\u2026" } }\n }\n}\n```'
|
|
4199
|
+
},
|
|
4200
|
+
{
|
|
4201
|
+
page: "ai",
|
|
4202
|
+
pageTitle: "Use with AI",
|
|
4203
|
+
heading: "Setup \u203A Codex CLI \u2014 `~/.codex/config.toml`",
|
|
4204
|
+
url: "https://bitpal.io/docs/#ai",
|
|
4205
|
+
text: '### Codex CLI \u2014 `~/.codex/config.toml`\n\n```\n[mcp_servers.bitpal]\ncommand = "npx"\nargs = ["-y", "@bitpal/mcp"]\nenv = { BITPAL_API_KEY = "bp_test_\u2026" }\n```'
|
|
4206
|
+
},
|
|
4207
|
+
{
|
|
4208
|
+
page: "ai",
|
|
4209
|
+
pageTitle: "Use with AI",
|
|
4210
|
+
heading: "Without MCP",
|
|
4211
|
+
url: "https://bitpal.io/docs/#ai",
|
|
4212
|
+
text: "## Without MCP\n\n- [llms.txt](/llms.txt) \u2014 an index of every page plus the rules below.\n- [llms-full.txt](/docs/llms-full.txt) \u2014 all docs in one file.\n- Every page has **Copy as Markdown** and a `.md` version (`/docs/md/<page>.md`).\n- [openapi.json](/docs/openapi.json) \u2014 the OpenAPI 3.1 spec, generated from the API code."
|
|
4213
|
+
},
|
|
4214
|
+
{
|
|
4215
|
+
page: "ai",
|
|
4216
|
+
pageTitle: "Use with AI",
|
|
4217
|
+
heading: "Rules for your AGENTS.md",
|
|
4218
|
+
url: "https://bitpal.io/docs/#ai",
|
|
4219
|
+
text: '## Rules for your AGENTS.md\n\nPaste this into `AGENTS.md`, `CLAUDE.md` or your editor\'s rules file so every session follows it.\n\n```\n## BitPal integration rules\n- Keep bp_test_/bp_live_ API keys and the whsec_ webhook secret on the server. Browsers only get checkout_url.\n- Fulfil orders from the checkout.session.paid webhook (signature verified) or a server-side session lookup \u2014\n never from the success_url redirect or a popup message.\n- Verify webhooks on the raw body with X-Webhook-Signature-256 + X-Webhook-Timestamp; await verifyWebhookSignature.\n- Webhooks are at-least-once and unordered: dedupe on the body id, keep the furthest session state.\n- Amounts are strings: amount = plain number in the line-item currency ("29"); amount_total = 6-decimal integer;\n deposit amounts use the token\'s decimals. No floating-point math.\n- Paid statuses are paid_confirmed and swept (there is no "paid"/"settled" status value).\n- Payment window: expires_in_minutes (1\u201360). Full payments up to 7 days late arrive with late: true \u2014 handle them.\n- Test keys take testnet chain ids only; live keys mainnet only.\n- Headless UIs call BitPal through your backend (CORS); only the public session endpoint works from the browser.\n- Send Idempotency-Key when creating sessions.\n- Docs: https://bitpal.io/docs/ \xB7 MCP: https://mcp.bitpal.io/mcp\n```'
|
|
4185
4220
|
},
|
|
4186
4221
|
{
|
|
4187
4222
|
page: "api",
|
|
@@ -4195,49 +4230,266 @@ var docs_data_default = {
|
|
|
4195
4230
|
pageTitle: "API reference",
|
|
4196
4231
|
heading: "Basics",
|
|
4197
4232
|
url: "https://bitpal.io/docs/#api",
|
|
4198
|
-
text: '## Basics\n\n| Topic | Details |\n| --- | --- |\n| Base URL | `https://api.bitpal.io` \u2014 test and live share the host; the API key prefix selects the mode. |\n| Auth | `Authorization: Bearer bp_test_\u2026` (test) or `bp_live_\u2026` (live). Keep keys on your server. |\n| Format | JSON. Success: `{ "data": \u2026 }` (authenticated endpoints add `meta: { mode, requestId }`). Amounts are strings. |\n| Errors | `{ "error", "code"
|
|
4233
|
+
text: '## Basics\n\n| Topic | Details |\n| --- | --- |\n| Base URL | `https://api.bitpal.io` \u2014 test and live share the host; the API key prefix selects the mode. |\n| Auth | `Authorization: Bearer bp_test_\u2026` (test) or `bp_live_\u2026` (live). Keep keys on your server. |\n| Format | JSON. Success: `{ "data": \u2026 }` (authenticated endpoints add `meta: { mode, requestId }`). Amounts are strings. |\n| Errors | `{ "error", "code", "retryable", "trace_id", "details"? }` \u2014 `error` is the human-readable message; branch on `code`. See [Errors](/docs/md/errors.md). |\n| Idempotency | `Idempotency-Key` (8\u2013128 chars) is honoured on every authenticated merchant `POST`; endpoints that do not enforce it say so. |'
|
|
4199
4234
|
},
|
|
4200
4235
|
{
|
|
4201
4236
|
page: "api",
|
|
4202
4237
|
pageTitle: "API reference",
|
|
4203
4238
|
heading: "Checkout sessions",
|
|
4204
4239
|
url: "https://bitpal.io/docs/#api",
|
|
4205
|
-
text: '## Checkout sessions\n\nCreate a session per order, send the buyer to the hosted page (or build your own UI), and track it.\n\n### `POST /v1/checkout/sessions` \u2014 Create a checkout session\n\nCreates a session for one order and returns the hosted `checkout_url`. Line items take either `amount` (plain decimal string, e.g. "29") or `amount_atomic` (6-decimal integer string), or reference a `price_id`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreateCheckoutSessionRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `line_items` | array of (object \\| object) | Yes | Raw items (`name`, `currency`, `amount` XOR `amount_atomic`) or price items (`price_id`). Do not mix the two kinds. (\u2265 1 items) |\n| `currency` | string | No | Session currency. Defaults to the line items\u2019 currency. (\u2265 1 chars) |\n| `success_url` | string (uri) | No | Where the buyer returns after paying. `{CHECKOUT_SESSION_ID}` is replaced with the session id. |\n| `cancel_url` | string (uri) | No | Where the buyer goes when cancelling. |\n| `customer_email` | string (email) | No | Buyer email for the receipt. |\n| `metadata` | map | No | Your key/value data, echoed back on the session. |\n| `expires_in_minutes` | integer | No | Payment window in minutes (1\u201360). Default 15. (1\u201360, default `15`) |\n| `tolerance_percent` | string | No | Accepted underpayment tolerance, as a percent string. |\n| `pay_chain` | string | No | Fix the payment network (CAIP-2, e.g. `eip155:8453`). Mutually exclusive with `allowed_pay_chains`. |\n| `allowed_pay_chains` | array of string | No | Networks the buyer may choose from (CAIP-2). (1\u20138 items) |\n| `allowed_assets` | array of object | No | Allowed (chain, token) cells. Takes precedence over `allowed_pay_chains`. (1\u201316 items) |\n\n```\n{\n "line_items": [\n {\n "name": "Pro plan",\n "amount": "29",\n "currency": "USDC"\n }\n ],\n "pay_chain": "eip155:84532",\n "expires_in_minutes": 30,\n "success_url": "https://your.store/thanks?session={CHECKOUT_SESSION_ID}"\n}\n```\n\n**Response `201`** \u2014 Session created.\n\n```\n{\n "data": {\n "id": "cs_Q2FxKhomCEju_OqvKfrQsTOh",\n "checkout_url": "https://bitpal.io/pay/cs_Q2FxKhomCEju_OqvKfrQsTOh?token=<session_token>",\n "session_token": "<session_token>",\n "status": "created",\n "amount_total": "29000000",\n "currency": "USDC",\n "expires_at": "2026-11-06T07:58:28.776Z",\n "tax": {\n "tax_amount": "0",\n "total_with_tax": "29000000"\n }\n },\n "meta": {\n "mode": "TEST",\n "requestId": "ec091faf-\u2026"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `409` \xB7 `422` \xB7 `429` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions` \u2014 List checkout sessions\n\nNewest first, in the API key\u2019s mode (test/live).\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `status` | query | string | No | Filter by status, or `succeeded` for all successful states. |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Sessions.\n\n```\n{\n "data": [\n {\n "id": "string",\n "status": "string",\n "mode": "TEST",\n "amount_total": "1000000",\n "amount_received": "string",\n "currency": "string",\n "pay_chain": "string",\n "pay_asset": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "deposit_address": "string",\n "refund_address": "string",\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "tx_hash": "string",\n "sweep_tx_hash": "string",\n "success_url": "string",\n "cancel_url": "string",\n "metadata": {},\n "payment_source": "string",\n "payment_link_id": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "raw_status": "string"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}` \u2014 Retrieve a checkout session\n\nServer-side source of truth for an order, together with webhooks.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 The session.\n\n```\n{\n "data": {\n "id": "string",\n "status": "string",\n "mode": "TEST",\n "amount_total": "1000000",\n "amount_received": "string",\n "currency": "string",\n "pay_chain": "string",\n "pay_asset": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "deposit_address": "string",\n "refund_address": "string",\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "tx_hash": "string",\n "sweep_tx_hash": "string",\n "success_url": "string",\n "cancel_url": "string",\n "metadata": {},\n "payment_source": "string",\n "payment_link_id": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "line_items": [\n {\n "id": "string",\n "session_id": "string",\n "name": "string",\n "amount": "1000000",\n "currency": "string",\n "quantity": 0,\n "price_id": "string",\n "product_id": "string",\n "created_at": "2026-11-06T07:00:00.000Z"\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}/status` \u2014 Get session status (public)\n\nLightweight status for polling. No authentication.\n\n**Auth:** None \u2014 public endpoint\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Status.\n\n```\n{\n "data": {\n "status": "awaiting_deposit",\n "paid_at": null\n }\n}\n```\n\n**Errors:** `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}/public` \u2014 Get the public session view\n\nBuyer-facing view used by the checkout page: payment options, deposit address once issued, refunds. No authentication; CORS-open.\n\n**Auth:** None \u2014 public endpoint\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Public view.\n\n```\n{\n "data": {\n "id": "string",\n "status": "string",\n "token_decimals": 0,\n "pay_amount": "string",\n "mode": "TEST",\n "chain_id": 0,\n "token_address": "string",\n "payment_options": [\n {\n "caip2": "string",\n "chain_id": 0,\n "token_symbol": "string",\n "token_address": "string",\n "decimals": 0,\n "amount_atomic": "string"\n }\n ],\n "currency": "string",\n "amount_total": "1000000",\n "amount_received": "string",\n "chain": "string",\n "tx_hash": "string",\n "release_tx_hash": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "success_url": "string",\n "cancel_url": "string",\n "metadata": {},\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "tax_amount": null,\n "tax_rate": null,\n "pay_chain": "string",\n "pay_asset": "string",\n "deposit_address": "string",\n "payment_method": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "line_items": [\n {\n "name": "string",\n "amount": "1000000",\n "currency": "string",\n "quantity": 0\n }\n ],\n "payment_source": "string",\n "refunds": [\n {\n "kind": "string",\n "amount": "string",\n "token_symbol": "string",\n "decimals": 0,\n "tx_hash": "string",\n "caip2": "string"\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `POST /v1/checkout/sessions/{id}/deposit-address` \u2014 Issue the deposit address\n\nFixes the buyer\u2019s network, token and refund address, and returns the one-time deposit address with the exact amount. Calling again returns the same address and keeps the first refund address. Requires the session token (header `X-Session-Token` or body `session_token`).\n\n**Auth:** Session token \u2014 `X-Session-Token` header or `session_token` body field (no API key)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n| `X-Session-Token` | header | string | No | Session token from the create-session response. Alternative to the `session_token` body field. |\n\n**Request body** (`IssueDepositAddressRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `session_token` | string | No | From the create-session response. May be sent as the `X-Session-Token` header instead. |\n| `refundAddress` | string | Yes | Buyer refund address in the chosen network\u2019s format (EVM 0x\u2026, Tron T\u2026, Solana base58). Cannot change later. |\n| `chain` | string | No | CAIP-2 or short name of the chosen network. Required for multi-option sessions. (\u2265 1 chars) |\n| `token` | string | No | Token symbol of the chosen asset, e.g. USDC. Required for multi-option sessions. (\u2265 1 chars) |\n\n```\n{\n "session_token": "<session_token>",\n "refundAddress": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",\n "chain": "eip155:84532",\n "token": "USDC"\n}\n```\n\n**Response `200`** \u2014 Deposit address.\n\n```\n{\n "data": {\n "sessionId": "cs_\u2026",\n "chain": "eip155:84532",\n "depositAddress": "0x6b19e871c934aB52775B6712e110f3e4b0626347",\n "expectedToken": "0x943b94dacf1507f2658f1cfe103ac36ebc13ef65",\n "expectedAmount": "29000000",\n "feeAmount": "435000",\n "refundAddress": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",\n "deadline": "1793951938",\n "expiresAt": "2026-11-06T07:58:28.776Z"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \xB7 `409` \xB7 `410` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/sessions/{id}/refunds` \u2014 List automatic refunds for a session\n\nOn-chain refunds BitPal made for this session (overpayment, expired underpayment, wrong token, late deposit).\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Refunds.\n\n```\n{\n "data": [\n {\n "id": "string",\n "kind": "string",\n "token": "string",\n "token_symbol": "string",\n "amount": "string",\n "amount_display": "string",\n "decimals": 0,\n "refund_address": "string",\n "tx_hash": "string",\n "caip2": "string",\n "block_number": "string",\n "created_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).\n\n### `GET /v1/checkout/fee-preview` \u2014 Preview the fee for an amount\n\nPass exactly one of `amount` or `amount_atomic`. The response is always atomic.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `amount` | query | string | No | Decimal string, e.g. "100". |\n| `amount_atomic` | query | string | No | 6-decimal integer string, e.g. "100000000". |\n\n**Response `200`** \u2014 Fee breakdown.\n\n```\n{\n "data": {\n "amount": "100000000",\n "fee_rate": "0.015",\n "fee_amount": "1500000",\n "net_amount": "98500000"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4240
|
+
text: "## Checkout sessions\n\nCreate a session per order, send the buyer to the hosted page (or build your own UI), and track it."
|
|
4241
|
+
},
|
|
4242
|
+
{
|
|
4243
|
+
page: "api",
|
|
4244
|
+
pageTitle: "API reference",
|
|
4245
|
+
heading: "Checkout sessions \u203A `POST /v1/checkout/sessions` \u2014 Create a checkout session",
|
|
4246
|
+
url: "https://bitpal.io/docs/#api",
|
|
4247
|
+
text: '### `POST /v1/checkout/sessions` \u2014 Create a checkout session\n\nCreates a session for one order and returns the hosted `checkout_url`. Line items take either `amount` (plain decimal string, e.g. "29") or `amount_atomic` (6-decimal integer string), or reference a `price_id`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreateCheckoutSessionRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `line_items` | array of (object \\| any \\| any) | Yes | Raw items (`name`, `currency`, `amount` XOR `amount_atomic`) or price items (`price_id`). Do not mix the two kinds. (\u2265 1 items) |\n| `currency` | string | No | Session currency. Defaults to the line items\u2019 currency. (\u2265 1 chars) |\n| `success_url` | string (uri) | No | Where the buyer returns after paying. `{CHECKOUT_SESSION_ID}` is replaced with the session id. (\u2264 2048 chars) |\n| `cancel_url` | string (uri) | No | Where the buyer goes when cancelling. (\u2264 2048 chars) |\n| `customer_email` | string (email) | No | Buyer email for the receipt. |\n| `metadata` | map | No | Your key/value data, echoed back on the session. |\n| `expires_in_minutes` | integer | No | Payment window in minutes (1\u201360). Default 15. (1\u201360, default `15`) |\n| `tolerance_percent` | string | No | Deprecated. Underpayments are never accepted \u2014 a short payment becomes `unresolved_underpaid` and can be topped up to the same address. Only `"0"` is accepted; any other value returns 400. |\n| `pay_chain` | string | No | Fix the payment network: CAIP-2 (`eip155:84532`) or a short name (`base`), which resolves to the testnet in test mode and the mainnet in live mode. Mutually exclusive with `allowed_pay_chains`. |\n| `allowed_pay_chains` | array of string | No | Networks the buyer may choose from (CAIP-2 or short names, resolved like `pay_chain`). (1\u20138 items) |\n| `allowed_assets` | array of object | No | Allowed (chain, token) cells. Takes precedence over `allowed_pay_chains`. (1\u201316 items) |\n\n```\n{\n "line_items": [\n {\n "name": "Pro plan",\n "amount": "29",\n "currency": "USDC"\n }\n ],\n "pay_chain": "eip155:84532",\n "expires_in_minutes": 30,\n "success_url": "https://your.store/thanks?session={CHECKOUT_SESSION_ID}"\n}\n```\n\n**Response `201`** \u2014 Session created.\n\n```\n{\n "data": {\n "id": "cs_Q2FxKhomCEju_OqvKfrQsTOh",\n "checkout_url": "https://bitpal.io/pay/cs_Q2FxKhomCEju_OqvKfrQsTOh?token=<session_token>",\n "session_token": "<session_token>",\n "status": "created",\n "amount_total": "29000000",\n "currency": "USDC",\n "expires_at": "2026-11-06T07:58:28.776Z",\n "tax": {\n "tax_amount": "0",\n "total_with_tax": "29000000"\n }\n },\n "meta": {\n "mode": "TEST",\n "requestId": "ec091faf-\u2026"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `409` \xB7 `422` \xB7 `429` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4248
|
+
},
|
|
4249
|
+
{
|
|
4250
|
+
page: "api",
|
|
4251
|
+
pageTitle: "API reference",
|
|
4252
|
+
heading: "Checkout sessions \u203A `GET /v1/checkout/sessions` \u2014 List checkout sessions",
|
|
4253
|
+
url: "https://bitpal.io/docs/#api",
|
|
4254
|
+
text: '### `GET /v1/checkout/sessions` \u2014 List checkout sessions\n\nNewest first, in the API key\u2019s mode (test/live).\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `status` | query | string | No | Filter by status, or `succeeded` for all successful states. |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Sessions.\n\n```\n{\n "data": [\n {\n "id": "string",\n "status": "string",\n "mode": "TEST",\n "amount_total": "1000000",\n "amount_received": "string",\n "currency": "string",\n "pay_chain": "string",\n "pay_asset": "string",\n "token_decimals": 0,\n "payment_method": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "deposit_address": "string",\n "refund_address": "string",\n "received_token": "string",\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "fee_amount_token": "string",\n "net_amount_token": "string",\n "tx_hash": "string",\n "deposit_tx_hash": "string",\n "sweep_tx_hash": "string",\n "success_url": "string",\n "cancel_url": "string",\n "metadata": {},\n "payment_source": "string",\n "payment_link_id": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "raw_status": "string"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4255
|
+
},
|
|
4256
|
+
{
|
|
4257
|
+
page: "api",
|
|
4258
|
+
pageTitle: "API reference",
|
|
4259
|
+
heading: "Checkout sessions \u203A `GET /v1/checkout/sessions/{id}` \u2014 Retrieve a checkout session",
|
|
4260
|
+
url: "https://bitpal.io/docs/#api",
|
|
4261
|
+
text: '### `GET /v1/checkout/sessions/{id}` \u2014 Retrieve a checkout session\n\nServer-side source of truth for an order, together with webhooks.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 The session.\n\n```\n{\n "data": {\n "id": "string",\n "status": "string",\n "mode": "TEST",\n "amount_total": "1000000",\n "amount_received": "string",\n "currency": "string",\n "pay_chain": "string",\n "pay_asset": "string",\n "token_decimals": 0,\n "payment_method": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "deposit_address": "string",\n "refund_address": "string",\n "received_token": "string",\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "fee_amount_token": "string",\n "net_amount_token": "string",\n "tx_hash": "string",\n "deposit_tx_hash": "string",\n "sweep_tx_hash": "string",\n "success_url": "string",\n "cancel_url": "string",\n "metadata": {},\n "payment_source": "string",\n "payment_link_id": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "line_items": [\n {\n "id": "string",\n "name": "string",\n "amount": "1000000",\n "currency": "string",\n "quantity": 0,\n "price_id": "string",\n "product_id": "string",\n "created_at": "2026-11-06T07:00:00.000Z"\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4262
|
+
},
|
|
4263
|
+
{
|
|
4264
|
+
page: "api",
|
|
4265
|
+
pageTitle: "API reference",
|
|
4266
|
+
heading: "Checkout sessions \u203A `GET /v1/checkout/sessions/{id}/status` \u2014 Get session status",
|
|
4267
|
+
url: "https://bitpal.io/docs/#api",
|
|
4268
|
+
text: '### `GET /v1/checkout/sessions/{id}/status` \u2014 Get session status\n\nLightweight status for polling from your server. Requires your API key (N13 \u2014 it is not a public endpoint). Buyer-facing pages should use `GET /v1/checkout/sessions/{id}/public`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Status.\n\n```\n{\n "data": {\n "status": "awaiting_deposit",\n "paid_at": null\n }\n}\n```\n\n**Errors:** `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4269
|
+
},
|
|
4270
|
+
{
|
|
4271
|
+
page: "api",
|
|
4272
|
+
pageTitle: "API reference",
|
|
4273
|
+
heading: "Checkout sessions \u203A `GET /v1/checkout/sessions/{id}/public` \u2014 Get the public session view",
|
|
4274
|
+
url: "https://bitpal.io/docs/#api",
|
|
4275
|
+
text: '### `GET /v1/checkout/sessions/{id}/public` \u2014 Get the public session view\n\nBuyer-facing view used by the checkout page: payment options, deposit address once issued, refunds. No authentication; CORS-open.\n\n**Auth:** None \u2014 public endpoint\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Public view.\n\n```\n{\n "data": {\n "id": "string",\n "status": "string",\n "token_decimals": 0,\n "pay_amount": "string",\n "mode": "TEST",\n "chain_id": 0,\n "token_address": "string",\n "payment_options": [\n {\n "caip2": "string",\n "chain_id": 0,\n "token_symbol": "string",\n "token_address": "string",\n "decimals": 0,\n "amount_atomic": "string"\n }\n ],\n "currency": "string",\n "amount_total": "1000000",\n "amount_received": "string",\n "chain": "string",\n "tx_hash": "string",\n "release_tx_hash": "string",\n "expires_at": "2026-11-06T07:00:00.000Z",\n "success_url": "string",\n "cancel_url": "string",\n "fee_amount": "string",\n "fee_rate": "string",\n "net_amount": "string",\n "tax_amount": null,\n "tax_rate": null,\n "pay_chain": "string",\n "pay_asset": "string",\n "deposit_address": "string",\n "payment_deadline_at": "2026-11-06T07:00:00.000Z",\n "payment_method": "string",\n "payer_wallet": "string",\n "seller_wallet": "string",\n "tolerance_percent": "string",\n "paid_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z",\n "created_at": "2026-11-06T07:00:00.000Z",\n "line_items": [\n {\n "name": "string",\n "amount": "1000000",\n "currency": "string",\n "quantity": 0\n }\n ],\n "payment_source": "string",\n "refunds": [\n {\n "kind": "string",\n "amount": "string",\n "token_symbol": "string",\n "decimals": 0,\n "tx_hash": "string",\n "caip2": "string"\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4276
|
+
},
|
|
4277
|
+
{
|
|
4278
|
+
page: "api",
|
|
4279
|
+
pageTitle: "API reference",
|
|
4280
|
+
heading: "Checkout sessions \u203A `POST /v1/checkout/sessions/{id}/deposit-address` \u2014 Issue the deposit address",
|
|
4281
|
+
url: "https://bitpal.io/docs/#api",
|
|
4282
|
+
text: '### `POST /v1/checkout/sessions/{id}/deposit-address` \u2014 Issue the deposit address\n\nFixes the buyer\u2019s network, token and refund address, and returns the one-time deposit address with the exact amount. Calling again returns the same address and keeps the first refund address. Requires the session token (header `X-Session-Token` or body `session_token`).\n\n**Auth:** Session token \u2014 `X-Session-Token` header or `session_token` body field (no API key)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n| `Idempotency-Key` | header | string | No | Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key. (8\u2013128 chars) |\n| `X-Session-Token` | header | string | No | Session token from the create-session response. Alternative to the `session_token` body field. |\n\n**Request body** (`IssueDepositAddressRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `session_token` | string | No | From the create-session response. May be sent as the `X-Session-Token` header instead. |\n| `refundAddress` | string | Yes | Buyer refund address in the chosen network\u2019s format (EVM 0x\u2026, Tron T\u2026, Solana base58). Cannot change later. |\n| `chain` | string | No | CAIP-2 or short name of the chosen network. Required for multi-option sessions. (\u2265 1 chars) |\n| `token` | string | No | Token symbol of the chosen asset, e.g. USDC. Required for multi-option sessions. (\u2265 1 chars) |\n\n```\n{\n "session_token": "<session_token>",\n "refundAddress": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",\n "chain": "eip155:84532",\n "token": "USDC"\n}\n```\n\n**Response `200`** \u2014 Deposit address.\n\n```\n{\n "data": {\n "sessionId": "cs_\u2026",\n "chain": "eip155:84532",\n "depositAddress": "0x6b19e871c934aB52775B6712e110f3e4b0626347",\n "expectedToken": "0x943b94dacf1507f2658f1cfe103ac36ebc13ef65",\n "expectedAmount": "29000000",\n "feeAmount": "435000",\n "refundAddress": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",\n "deadline": "1793951938",\n "expiresAt": "2026-11-06T07:58:28.776Z"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \xB7 `409` \xB7 `410` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4283
|
+
},
|
|
4284
|
+
{
|
|
4285
|
+
page: "api",
|
|
4286
|
+
pageTitle: "API reference",
|
|
4287
|
+
heading: "Checkout sessions \u203A `GET /v1/checkout/sessions/{id}/refunds` \u2014 List automatic refunds for a session",
|
|
4288
|
+
url: "https://bitpal.io/docs/#api",
|
|
4289
|
+
text: '### `GET /v1/checkout/sessions/{id}/refunds` \u2014 List automatic refunds for a session\n\nOn-chain refunds BitPal made for this session (overpayment, expired underpayment, wrong token, late deposit).\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Checkout session id (`cs_\u2026`). |\n\n**Response `200`** \u2014 Refunds.\n\n```\n{\n "data": [\n {\n "id": "string",\n "kind": "string",\n "token": "string",\n "token_symbol": "string",\n "amount": "string",\n "amount_display": "string",\n "decimals": 0,\n "refund_address": "string",\n "tx_hash": "string",\n "caip2": "string",\n "block_number": "string",\n "created_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4290
|
+
},
|
|
4291
|
+
{
|
|
4292
|
+
page: "api",
|
|
4293
|
+
pageTitle: "API reference",
|
|
4294
|
+
heading: "Checkout sessions \u203A `GET /v1/checkout/fee-preview` \u2014 Preview the fee for an amount",
|
|
4295
|
+
url: "https://bitpal.io/docs/#api",
|
|
4296
|
+
text: '### `GET /v1/checkout/fee-preview` \u2014 Preview the fee for an amount\n\nPass exactly one of `amount` or `amount_atomic`. The response is always atomic.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `amount` | query | string | No | Decimal string, e.g. "100". |\n| `amount_atomic` | query | string | No | 6-decimal integer string, e.g. "100000000". |\n\n**Response `200`** \u2014 Fee breakdown.\n\n```\n{\n "data": {\n "amount": "100000000",\n "fee_rate": "0.015",\n "fee_amount": "1500000",\n "net_amount": "98500000"\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4206
4297
|
},
|
|
4207
4298
|
{
|
|
4208
4299
|
page: "api",
|
|
4209
4300
|
pageTitle: "API reference",
|
|
4210
4301
|
heading: "Products & prices",
|
|
4211
4302
|
url: "https://bitpal.io/docs/#api",
|
|
4212
|
-
text:
|
|
4303
|
+
text: "## Products & prices\n\nCatalog objects. Prices are immutable."
|
|
4304
|
+
},
|
|
4305
|
+
{
|
|
4306
|
+
page: "api",
|
|
4307
|
+
pageTitle: "API reference",
|
|
4308
|
+
heading: "Products & prices \u203A `POST /v1/checkout/products` \u2014 Create a product",
|
|
4309
|
+
url: "https://bitpal.io/docs/#api",
|
|
4310
|
+
text: '### `POST /v1/checkout/products` \u2014 Create a product\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreateProductRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | Yes | 1\u2013200 chars |\n| `description` | string | No | \u2264 2000 chars |\n| `image_url` | string (uri) | No | \u2264 2048 chars |\n| `metadata` | map | No | |\n\n```\n{\n "name": "Pro plan"\n}\n```\n\n**Response `201`** \u2014 Product created.\n\n```\n{\n "data": {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4311
|
+
},
|
|
4312
|
+
{
|
|
4313
|
+
page: "api",
|
|
4314
|
+
pageTitle: "API reference",
|
|
4315
|
+
heading: "Products & prices \u203A `GET /v1/checkout/products` \u2014 List products",
|
|
4316
|
+
url: "https://bitpal.io/docs/#api",
|
|
4317
|
+
text: '### `GET /v1/checkout/products` \u2014 List products\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `status` | query | "active" \\| "inactive" | No | |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Products.\n\n```\n{\n "data": [\n {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4318
|
+
},
|
|
4319
|
+
{
|
|
4320
|
+
page: "api",
|
|
4321
|
+
pageTitle: "API reference",
|
|
4322
|
+
heading: "Products & prices \u203A `GET /v1/checkout/products/{id}` \u2014 Retrieve a product",
|
|
4323
|
+
url: "https://bitpal.io/docs/#api",
|
|
4324
|
+
text: '### `GET /v1/checkout/products/{id}` \u2014 Retrieve a product\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n\n**Response `200`** \u2014 The product.\n\n```\n{\n "data": {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4325
|
+
},
|
|
4326
|
+
{
|
|
4327
|
+
page: "api",
|
|
4328
|
+
pageTitle: "API reference",
|
|
4329
|
+
heading: "Products & prices \u203A `PATCH /v1/checkout/products/{id}` \u2014 Update a product",
|
|
4330
|
+
url: "https://bitpal.io/docs/#api",
|
|
4331
|
+
text: '### `PATCH /v1/checkout/products/{id}` \u2014 Update a product\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n\n**Request body** (`UpdateProductRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | No | 1\u2013200 chars |\n| `description` | string \\| null | No | \u2264 2000 chars |\n| `image_url` | string (uri) \\| null | No | \u2264 2048 chars |\n| `metadata` | map | No | |\n| `status` | "active" \\| "inactive" | No | |\n\n**Response `200`** \u2014 The updated product.\n\n```\n{\n "data": {\n "id": "string",\n "name": "string",\n "description": "string",\n "image_url": "string",\n "status": "active",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4332
|
+
},
|
|
4333
|
+
{
|
|
4334
|
+
page: "api",
|
|
4335
|
+
pageTitle: "API reference",
|
|
4336
|
+
heading: "Products & prices \u203A `POST /v1/checkout/products/{id}/prices` \u2014 Create a price",
|
|
4337
|
+
url: "https://bitpal.io/docs/#api",
|
|
4338
|
+
text: '### `POST /v1/checkout/products/{id}/prices` \u2014 Create a price\n\nPrices are immutable: to change an amount, create a new price. Pass exactly one of `amount` (decimal string) or `amount_atomic` (6-decimal integer string). The currency is derived from `token_symbol`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreatePriceRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `amount` | string | No | Decimal string, e.g. "29". Mutually exclusive with `amount_atomic`. |\n| `amount_atomic` | string | No | 6-decimal integer string ("29000000" = 29). |\n| `token_symbol` | "USDC" \\| "USDT" \\| "JPYC" | No | The price\u2019s asset. The currency is derived from it. (default `"USDC"`) |\n| `billing_type` | "one_time" | No | Only `one_time` for now. (default `"one_time"`) |\n| `nickname` | string | No | Display name. (\u2264 200 chars) |\n| `lookup_key` | string | No | Your own stable key for this price. (1\u2013200 chars) |\n\n```\n{\n "amount": "29",\n "token_symbol": "USDC"\n}\n```\n\n**Response `201`** \u2014 Price created.\n\n```\n{\n "data": {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4339
|
+
},
|
|
4340
|
+
{
|
|
4341
|
+
page: "api",
|
|
4342
|
+
pageTitle: "API reference",
|
|
4343
|
+
heading: "Products & prices \u203A `GET /v1/checkout/products/{id}/prices` \u2014 List a product\u2019s prices",
|
|
4344
|
+
url: "https://bitpal.io/docs/#api",
|
|
4345
|
+
text: '### `GET /v1/checkout/products/{id}/prices` \u2014 List a product\u2019s prices\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Product id (`prod_\u2026`). |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Prices.\n\n```\n{\n "data": [\n {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4346
|
+
},
|
|
4347
|
+
{
|
|
4348
|
+
page: "api",
|
|
4349
|
+
pageTitle: "API reference",
|
|
4350
|
+
heading: "Products & prices \u203A `GET /v1/checkout/prices/{id}` \u2014 Retrieve a price",
|
|
4351
|
+
url: "https://bitpal.io/docs/#api",
|
|
4352
|
+
text: '### `GET /v1/checkout/prices/{id}` \u2014 Retrieve a price\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Price id (`price_\u2026`). |\n\n**Response `200`** \u2014 The price.\n\n```\n{\n "data": {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4353
|
+
},
|
|
4354
|
+
{
|
|
4355
|
+
page: "api",
|
|
4356
|
+
pageTitle: "API reference",
|
|
4357
|
+
heading: "Products & prices \u203A `PATCH /v1/checkout/prices/{id}` \u2014 Update a price\u2019s nickname",
|
|
4358
|
+
url: "https://bitpal.io/docs/#api",
|
|
4359
|
+
text: '### `PATCH /v1/checkout/prices/{id}` \u2014 Update a price\u2019s nickname\n\nOnly `nickname` can change. Sending an amount/currency field returns `PRICE_IMMUTABLE_FIELD`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Price id (`price_\u2026`). |\n\n**Request body** (`UpdatePriceRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `nickname` | string \\| null | No | \u2264 200 chars |\n\n**Response `200`** \u2014 The updated price.\n\n```\n{\n "data": {\n "id": "string",\n "product_id": "string",\n "amount_atomic": "1000000",\n "currency": "string",\n "token_symbol": "string",\n "billing_type": "one_time",\n "nickname": "string",\n "lookup_key": "string",\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4213
4360
|
},
|
|
4214
4361
|
{
|
|
4215
4362
|
page: "api",
|
|
4216
4363
|
pageTitle: "API reference",
|
|
4217
4364
|
heading: "Payment links",
|
|
4218
4365
|
url: "https://bitpal.io/docs/#api",
|
|
4219
|
-
text:
|
|
4366
|
+
text: "## Payment links\n\nShareable hosted URLs for a price."
|
|
4367
|
+
},
|
|
4368
|
+
{
|
|
4369
|
+
page: "api",
|
|
4370
|
+
pageTitle: "API reference",
|
|
4371
|
+
heading: "Payment links \u203A `POST /v1/checkout/payment-links` \u2014 Create a payment link",
|
|
4372
|
+
url: "https://bitpal.io/docs/#api",
|
|
4373
|
+
text: '### `POST /v1/checkout/payment-links` \u2014 Create a payment link\n\nA shareable hosted URL for one price. The link \u2194 price binding is fixed after creation.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreatePaymentLinkRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `price_id` | string | Yes | The price this link sells (`price_\u2026`). |\n| `title` | string | No | 1\u2013200 chars |\n| `description` | string | No | \u2264 2000 chars |\n| `collect_email` | boolean | No | Ask the buyer for an email. Default true. (default `true`) |\n| `email_required` | boolean | No | Require the email: the hosted link page asks for it and no checkout is created without it. Default true; needs `collect_email`. (default `true`) |\n| `allowed_pay_chains` | array of string | No | Networks the buyer may choose from (CAIP-2 or short names). (1\u20138 items) |\n| `allowed_assets` | array of object | No | Allowed (chain, token) cells. Takes precedence over `allowed_pay_chains`. (1\u201316 items) |\n| `success_url` | string (uri) | No | \u2264 2048 chars |\n| `cancel_url` | string (uri) | No | \u2264 2048 chars |\n| `max_uses` | integer | No | Stop accepting payments after this many paid uses. (\u2265 1) |\n| `expires_at` | string (date-time) | No | ISO-8601 time after which the link stops working. |\n| `metadata` | map | No | |\n\n```\n{\n "price_id": "price_\u2026",\n "allowed_pay_chains": [\n "eip155:8453"\n ]\n}\n```\n\n**Response `201`** \u2014 Link created.\n\n```\n{\n "data": {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4374
|
+
},
|
|
4375
|
+
{
|
|
4376
|
+
page: "api",
|
|
4377
|
+
pageTitle: "API reference",
|
|
4378
|
+
heading: "Payment links \u203A `GET /v1/checkout/payment-links` \u2014 List payment links",
|
|
4379
|
+
url: "https://bitpal.io/docs/#api",
|
|
4380
|
+
text: '### `GET /v1/checkout/payment-links` \u2014 List payment links\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `status` | query | "active" \\| "inactive" | No | |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Links.\n\n```\n{\n "data": [\n {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4381
|
+
},
|
|
4382
|
+
{
|
|
4383
|
+
page: "api",
|
|
4384
|
+
pageTitle: "API reference",
|
|
4385
|
+
heading: "Payment links \u203A `GET /v1/checkout/payment-links/{id}` \u2014 Retrieve a payment link",
|
|
4386
|
+
url: "https://bitpal.io/docs/#api",
|
|
4387
|
+
text: '### `GET /v1/checkout/payment-links/{id}` \u2014 Retrieve a payment link\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Payment link id (`plink_\u2026`). |\n\n**Response `200`** \u2014 The link, with product/price summary fields.\n\n```\n{\n "data": {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4388
|
+
},
|
|
4389
|
+
{
|
|
4390
|
+
page: "api",
|
|
4391
|
+
pageTitle: "API reference",
|
|
4392
|
+
heading: "Payment links \u203A `PATCH /v1/checkout/payment-links/{id}` \u2014 Update a payment link",
|
|
4393
|
+
url: "https://bitpal.io/docs/#api",
|
|
4394
|
+
text: '### `PATCH /v1/checkout/payment-links/{id}` \u2014 Update a payment link\n\nOperational settings and active \u2194 inactive. The price cannot change \u2014 create a new link instead.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Payment link id (`plink_\u2026`). |\n\n**Request body** (`UpdatePaymentLinkRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `title` | string \\| null | No | 1\u2013200 chars |\n| `description` | string \\| null | No | \u2264 2000 chars |\n| `collect_email` | boolean | No | |\n| `email_required` | boolean | No | |\n| `allowed_pay_chains` | array of string \\| null | No | 1\u20138 items |\n| `allowed_assets` | array of object \\| null | No | 1\u201316 items |\n| `success_url` | string (uri) \\| null | No | \u2264 2048 chars |\n| `cancel_url` | string (uri) \\| null | No | \u2264 2048 chars |\n| `status` | "active" \\| "inactive" | No | |\n| `max_uses` | integer \\| null | No | \u2265 1 |\n| `expires_at` | string (date-time) \\| null | No | |\n| `metadata` | map | No | |\n\n**Response `200`** \u2014 The updated link.\n\n```\n{\n "data": {\n "id": "string",\n "share_token": "string",\n "price_id": "string",\n "title": "string",\n "description": "string",\n "collect_email": true,\n "email_required": true,\n "allowed_pay_chains": [\n "string"\n ],\n "allowed_assets": [\n {\n "caip2": "string",\n "token": "string"\n }\n ],\n "success_url": "string",\n "cancel_url": "string",\n "status": "active",\n "max_uses": 0,\n "paid_use_count": 0,\n "expires_at": "2026-11-06T07:00:00.000Z",\n "metadata": {},\n "mode": "TEST",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `404` \xB7 `409` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4220
4395
|
},
|
|
4221
4396
|
{
|
|
4222
4397
|
page: "api",
|
|
4223
4398
|
pageTitle: "API reference",
|
|
4224
4399
|
heading: "x402",
|
|
4225
4400
|
url: "https://bitpal.io/docs/#api",
|
|
4226
|
-
text:
|
|
4401
|
+
text: "## x402\n\nCharge per API call with HTTP 402 and EIP-3009 authorizations."
|
|
4402
|
+
},
|
|
4403
|
+
{
|
|
4404
|
+
page: "api",
|
|
4405
|
+
pageTitle: "API reference",
|
|
4406
|
+
heading: "x402 \u203A `GET /v1/x402/assets` \u2014 List x402 assets for this mode",
|
|
4407
|
+
url: "https://bitpal.io/docs/#api",
|
|
4408
|
+
text: '### `GET /v1/x402/assets` \u2014 List x402 assets for this mode\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Response `200`** \u2014 Assets.\n\n```\n{\n "data": {\n "mode": "TEST",\n "assets": [\n {\n "caip2": "string",\n "network": "string",\n "chain_name": "string",\n "symbol": "string",\n "decimals": 0,\n "address": "string"\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4409
|
+
},
|
|
4410
|
+
{
|
|
4411
|
+
page: "api",
|
|
4412
|
+
pageTitle: "API reference",
|
|
4413
|
+
heading: "x402 \u203A `POST /v1/x402/requirements` \u2014 Build the 402 payment requirements",
|
|
4414
|
+
url: "https://bitpal.io/docs/#api",
|
|
4415
|
+
text: '### `POST /v1/x402/requirements` \u2014 Build the 402 payment requirements\n\nReturns the body your resource server sends with `402 Payment Required`, including the fee split. Pass exactly one of `amount` or `amountAtomic`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`X402RequirementsRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `network` | string | No | x402 network name (`base`, `base-sepolia`). Defaults to the mode\u2019s first asset. (\u2265 1 chars) |\n| `asset` | string | No | Token symbol. Default USDC. (\u2265 1 chars, default `"USDC"`) |\n| `payTo` | string | Yes | Your payout wallet (0x) \u2014 must be the one registered in Console for this mode, otherwise `PAYTO_NOT_MERCHANT_WALLET`. |\n| `amount` | string | No | Gross amount per call as a decimal string, e.g. "0.1". The fee is taken from it. |\n| `amountAtomic` | string | No | Same as `amount`, as a 6-decimal integer string. |\n| `resource` | string | Yes | The URL being paid for. (1\u20132048 chars) |\n| `description` | string | No | \u2264 512 chars |\n| `mimeType` | string | No | \u2264 128 chars |\n| `maxTimeoutSeconds` | integer | No | How long the payer has to sign (max 3600). Default 300. (\u2264 3600, > 0) |\n\n```\n{\n "payTo": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",\n "amount": "0.1",\n "resource": "https://api.example.com/report"\n}\n```\n\n**Response `200`** \u2014 402 body.\n\n```\n{\n "data": {\n "x402Version": 1,\n "accepts": [\n {\n "scheme": "exact",\n "network": "string",\n "asset": "string",\n "payTo": "string",\n "maxAmountRequired": "1000000",\n "resource": "string",\n "description": "string",\n "mimeType": "string",\n "maxTimeoutSeconds": 0,\n "extra": {\n "name": "string",\n "version": "string",\n "chainId": 0\n },\n "feeBreakdown": {\n "netAmount": "1000000",\n "feeAmount": "1000000",\n "feeBps": 0,\n "feeRecipient": "string"\n }\n }\n ]\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4416
|
+
},
|
|
4417
|
+
{
|
|
4418
|
+
page: "api",
|
|
4419
|
+
pageTitle: "API reference",
|
|
4420
|
+
heading: "x402 \u203A `POST /v1/x402/verify` \u2014 Verify an X-PAYMENT header",
|
|
4421
|
+
url: "https://bitpal.io/docs/#api",
|
|
4422
|
+
text: '### `POST /v1/x402/verify` \u2014 Verify an X-PAYMENT header\n\nChecks the EIP-3009 authorization against the requirements. Sends nothing on-chain.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`X402PaymentRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `x402Version` | 1 | Yes | |\n| `paymentPayload` | object | Yes | |\n| `paymentRequirements` | object | Yes | |\n\n**Response `200`** \u2014 Valid.\n\n```\n{\n "data": {\n "valid": true,\n "payment_id": "string",\n "status": "string",\n "idempotent": true\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `409` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4423
|
+
},
|
|
4424
|
+
{
|
|
4425
|
+
page: "api",
|
|
4426
|
+
pageTitle: "API reference",
|
|
4427
|
+
heading: "x402 \u203A `POST /v1/x402/settle` \u2014 Settle an x402 payment",
|
|
4428
|
+
url: "https://bitpal.io/docs/#api",
|
|
4429
|
+
text: '### `POST /v1/x402/settle` \u2014 Settle an x402 payment\n\nBroadcasts the transfer and waits for the receipt. Serve the resource only when `status` is `confirmed`. Settlement is idempotent per authorization \u2014 retry with the same body on 5xx.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`X402PaymentRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `x402Version` | 1 | Yes | |\n| `paymentPayload` | object | Yes | |\n| `paymentRequirements` | object | Yes | |\n\n**Response `200`** \u2014 Settlement result.\n\n```\n{\n "data": {\n "success": true,\n "payment_id": "string",\n "status": "string",\n "tx_hash": "string",\n "fee_tx_hash": "string",\n "network": "string",\n "idempotent": true\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `403` \xB7 `409` \xB7 `503` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4430
|
+
},
|
|
4431
|
+
{
|
|
4432
|
+
page: "api",
|
|
4433
|
+
pageTitle: "API reference",
|
|
4434
|
+
heading: "x402 \u203A `GET /v1/x402/payments/{id}` \u2014 Retrieve an x402 payment",
|
|
4435
|
+
url: "https://bitpal.io/docs/#api",
|
|
4436
|
+
text: '### `GET /v1/x402/payments/{id}` \u2014 Retrieve an x402 payment\n\nRecovery path when a settle response was lost \u2014 check here before settling again.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Payment id. |\n\n**Response `200`** \u2014 The payment.\n\n```\n{\n "data": {\n "id": "string",\n "status": "pending",\n "raw_status": "string",\n "resource": "string",\n "chain": "string",\n "asset": "string",\n "decimals": 0,\n "amount": "1000000",\n "gross_amount": "1000000",\n "payer": "string",\n "pay_to": "string",\n "tx_hash": "string",\n "created_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4437
|
+
},
|
|
4438
|
+
{
|
|
4439
|
+
page: "api",
|
|
4440
|
+
pageTitle: "API reference",
|
|
4441
|
+
heading: "x402 \u203A `GET /v1/x402/payments` \u2014 List x402 payments",
|
|
4442
|
+
url: "https://bitpal.io/docs/#api",
|
|
4443
|
+
text: '### `GET /v1/x402/payments` \u2014 List x402 payments\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `limit` | query | integer | No | \u2264 100, > 0, default `20` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n| `status` | query | string | No | pending \\| confirmed \\| failed \\| needs_review |\n\n**Response `200`** \u2014 Payments.\n\n```\n{\n "data": [\n {\n "id": "string",\n "status": "pending",\n "raw_status": "string",\n "resource": "string",\n "chain": "string",\n "asset": "string",\n "decimals": 0,\n "amount": "1000000",\n "gross_amount": "1000000",\n "payer": "string",\n "pay_to": "string",\n "tx_hash": "string",\n "created_at": "2026-11-06T07:00:00.000Z",\n "settled_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4227
4444
|
},
|
|
4228
4445
|
{
|
|
4229
4446
|
page: "api",
|
|
4230
4447
|
pageTitle: "API reference",
|
|
4231
4448
|
heading: "Webhook endpoints",
|
|
4232
4449
|
url: "https://bitpal.io/docs/#api",
|
|
4233
|
-
text:
|
|
4450
|
+
text: "## Webhook endpoints\n\nManage where BitPal delivers signed events."
|
|
4451
|
+
},
|
|
4452
|
+
{
|
|
4453
|
+
page: "api",
|
|
4454
|
+
pageTitle: "API reference",
|
|
4455
|
+
heading: "Webhook endpoints \u203A `POST /v1/webhooks` \u2014 Create a webhook endpoint",
|
|
4456
|
+
url: "https://bitpal.io/docs/#api",
|
|
4457
|
+
text: '### `POST /v1/webhooks` \u2014 Create a webhook endpoint\n\n**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403. The response carries the `whsec_\u2026` signing secret \u2014 it is shown only once.\n\n**Auth:** Console login only \u2014 manage this in Console \u2192 Developers \u2192 Webhooks (API keys get 403)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`CreateWebhookEndpointRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `endpoint_url` | string (uri) | Yes | HTTPS URL that receives events. Private hosts are rejected. |\n| `subscribed_events` | array of string | Yes | Event names to deliver (see the `webhooks` section). (\u2265 1 items) |\n| `retry_policy` | object | No | `max_retries` caps the total delivery attempts (1\u201320, default 10: 3 right away, then 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h). The schedule itself is fixed; `backoff` is ignored. |\n| `dead_letter_destination` | string (uri) \\| null | No | Optional URL for dead-lettered deliveries. |\n\n```\n{\n "endpoint_url": "https://your.store/webhooks/bitpal",\n "subscribed_events": [\n "checkout.session.paid"\n ]\n}\n```\n\n**Response `201`** \u2014 Endpoint created.\n\n```\n{\n "data": {\n "id": "00000000-0000-4000-8000-000000000000",\n "mode": "TEST",\n "endpoint_url": "string",\n "subscribed_events": [\n "string"\n ],\n "retry_policy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "dead_letter_destination": "string",\n "status": "active",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z",\n "secret": "string"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4458
|
+
},
|
|
4459
|
+
{
|
|
4460
|
+
page: "api",
|
|
4461
|
+
pageTitle: "API reference",
|
|
4462
|
+
heading: "Webhook endpoints \u203A `GET /v1/webhooks` \u2014 List webhook endpoints",
|
|
4463
|
+
url: "https://bitpal.io/docs/#api",
|
|
4464
|
+
text: '### `GET /v1/webhooks` \u2014 List webhook endpoints\n\n**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403. Endpoints of the API key\u2019s environment and mode. Note: list items use camelCase keys.\n\n**Auth:** Console login only \u2014 manage this in Console \u2192 Developers \u2192 Webhooks (API keys get 403)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `limit` | query | integer | No | 1\u2013100, default `50` |\n| `offset` | query | integer | No | \u2265 0, default `0` |\n\n**Response `200`** \u2014 Endpoints, newest first. `meta` carries total/limit/offset.\n\n```\n{\n "data": [\n {\n "id": "00000000-0000-4000-8000-000000000000",\n "mode": "TEST",\n "endpoint_url": "string",\n "subscribed_events": [\n "string"\n ],\n "retry_policy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "dead_letter_destination": "string",\n "status": "active",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n }\n ],\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `401` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4465
|
+
},
|
|
4466
|
+
{
|
|
4467
|
+
page: "api",
|
|
4468
|
+
pageTitle: "API reference",
|
|
4469
|
+
heading: "Webhook endpoints \u203A `PATCH /v1/webhooks/{id}` \u2014 Update a webhook endpoint",
|
|
4470
|
+
url: "https://bitpal.io/docs/#api",
|
|
4471
|
+
text: '### `PATCH /v1/webhooks/{id}` \u2014 Update a webhook endpoint\n\n**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403. Setting `status` to `disabled` removes the endpoint and requires `reason_code` (otherwise 400 `REASON_CODE_REQUIRED`). It is recorded in the audit log.\n\n**Auth:** Console login only \u2014 manage this in Console \u2192 Developers \u2192 Webhooks (API keys get 403)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Webhook endpoint id (uuid). |\n\n**Request body** (`UpdateWebhookEndpointRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `endpoint_url` | string (uri) | No | |\n| `subscribed_events` | array of string | No | |\n| `status` | "active" \\| "paused" \\| "disabled" | No | |\n| `retry_policy` | object | No | |\n| `dead_letter_destination` | string (uri) \\| null | No | |\n| `reason_code` | "user_request" \\| "security_concern" \\| "rotation" \\| "no_longer_needed" \\| "other" | No | Required when `status` is `disabled`: one of user_request, security_concern, rotation, no_longer_needed, other. |\n| `reason_note` | string | No | Optional free text (\u2264 500 chars) stored with the audit record. (\u2264 500 chars) |\n\n**Response `200`** \u2014 The updated endpoint.\n\n```\n{\n "data": {\n "id": "00000000-0000-4000-8000-000000000000",\n "mode": "TEST",\n "endpoint_url": "string",\n "subscribed_events": [\n "string"\n ],\n "retry_policy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "dead_letter_destination": "string",\n "status": "active",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4472
|
+
},
|
|
4473
|
+
{
|
|
4474
|
+
page: "api",
|
|
4475
|
+
pageTitle: "API reference",
|
|
4476
|
+
heading: "Webhook endpoints \u203A `POST /v1/webhooks/{id}/rotate-secret` \u2014 Rotate the signing secret",
|
|
4477
|
+
url: "https://bitpal.io/docs/#api",
|
|
4478
|
+
text: '### `POST /v1/webhooks/{id}/rotate-secret` \u2014 Rotate the signing secret\n\n**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403. Takes effect immediately. The new secret is returned once \u2014 update your verifier. Requires `reason_code` (400 `REASON_CODE_REQUIRED` if missing); recorded in the audit log.\n\n**Auth:** Console login only \u2014 manage this in Console \u2192 Developers \u2192 Webhooks (API keys get 403)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `id` | path | string | Yes | Webhook endpoint id (uuid). |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`RotateWebhookSecretRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `reason_code` | "user_request" \\| "security_concern" \\| "rotation" \\| "no_longer_needed" \\| "other" | Yes | One of user_request, security_concern, rotation, no_longer_needed, other. |\n| `reason_note` | string | No | Optional free text (\u2264 500 chars) stored with the audit record. (\u2264 500 chars) |\n\n```\n{\n "reason_code": "rotation"\n}\n```\n\n**Response `200`** \u2014 Endpoint with the new secret.\n\n```\n{\n "data": {\n "id": "00000000-0000-4000-8000-000000000000",\n "mode": "TEST",\n "endpoint_url": "string",\n "subscribed_events": [\n "string"\n ],\n "retry_policy": {\n "max_retries": 0,\n "backoff": "exponential"\n },\n "dead_letter_destination": "string",\n "status": "active",\n "created_at": "2026-11-06T07:00:00.000Z",\n "updated_at": "2026-11-06T07:00:00.000Z",\n "secret": "string"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4479
|
+
},
|
|
4480
|
+
{
|
|
4481
|
+
page: "api",
|
|
4482
|
+
pageTitle: "API reference",
|
|
4483
|
+
heading: "Webhook endpoints \u203A `POST /v1/developers/webhooks/test` \u2014 Send a test event",
|
|
4484
|
+
url: "https://bitpal.io/docs/#api",
|
|
4485
|
+
text: '### `POST /v1/developers/webhooks/test` \u2014 Send a test event\n\nPosts one signed sample event to an endpoint (no retries). The envelope id is prefixed `test_`, `data.test` is true and the request carries `X-Webhook-Test: true`.\n\n**Auth:** API key \u2014 `Authorization: Bearer bp_test_\u2026` / `bp_live_\u2026` (server only)\n\n**Parameters**\n\n| Parameter | In | Type | Required | Description |\n| --- | --- | --- | --- | --- |\n| `Idempotency-Key` | header | string | No | 8\u2013128 characters, unique per operation. A retry with the same key and body within 24 hours returns the original response with `X-Idempotency-Replayed: true`. (8\u2013128 chars) |\n\n**Request body** (`SendTestWebhookRequest`)\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `webhook_id` | string (uuid) | Yes | Endpoint id. |\n| `event_type` | string | Yes | Event name to put in the envelope, e.g. checkout.session.paid. (1\u2013200 chars) |\n| `payload` | map | No | Custom `data` object. Defaults to a sample message. |\n\n```\n{\n "webhook_id": "00000000-0000-4000-8000-000000000000",\n "event_type": "checkout.session.paid"\n}\n```\n\n**Response `200`** \u2014 Delivery result.\n\n```\n{\n "data": {\n "delivery_id": "00000000-0000-4000-8000-000000000000",\n "webhook_id": "00000000-0000-4000-8000-000000000000",\n "endpoint_url": "string",\n "event_type": "string",\n "http_status": 0,\n "response_body": "string",\n "error": "string",\n "duration_ms": 0,\n "test": true,\n "sent_at": "2026-11-06T07:00:00.000Z"\n },\n "meta": {\n "mode": "TEST",\n "requestId": "string",\n "total": 0,\n "limit": 0,\n "offset": 0\n }\n}\n```\n\n**Errors:** `400` \xB7 `401` \xB7 `404` \xB7 `409` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4234
4486
|
},
|
|
4235
4487
|
{
|
|
4236
4488
|
page: "api",
|
|
4237
4489
|
pageTitle: "API reference",
|
|
4238
4490
|
heading: "Webhook events",
|
|
4239
4491
|
url: "https://bitpal.io/docs/#api",
|
|
4240
|
-
text: "## Webhook events\n\nEnvelope: `{ id, event, created_at, data }`, signed with `X-Webhook-Signature-256`. Verification and retries: [Webhooks](/docs/md/webhooks.md).\n\n| Event | Fires when | `data` fields |\n| --- | --- | --- |\n| `checkout.session.created` | A session is created. | `session_id`, `amount_total`, `currency`, `late` |\n| `checkout.session.deposit_detected` | Funds seen on-chain, awaiting confirmations. | `session_id`, `amount_total`, `currency`, `late`, `tx_hash`, `amount_received`, `received_token` |\n| `checkout.session.paid` | Confirmed \u2014 safe to fulfil. `late: true` if it arrived after the deadline. | `session_id`, `amount_total`, `currency`, `late`, `tx_hash` |\n| `checkout.session.settled` | Funds swept to your payout wallet. | `session_id`, `amount_total`, `currency`, `late`, `sweep_tx_hash` |\n| `checkout.session.unresolved` | Underpaid, wrong token, or a confirmed deposit reverted by a reorg (`reason`). | `session_id`, `amount_total`, `currency`, `late`, `reason`, `amount_expected`, `amount_received`, `received_token`, `tx_hash` |\n| `checkout.session.expired` | Deadline passed without full payment. | `session_id`, `amount_total`, `currency`, `late`, `reason`, `refund_tx_hash` |\n| `checkout.refund.recorded` | A refund was made on-chain (`kind`). | `session_id`, `kind`, `amount`, `token`, `tx_hash`, `refund_address` |\n| `x402.payment.resolved` | An x402 call that settled with `503` reached its outcome. Only fires for those \u2014 the normal path answers inline. | `payment_id`, `status`, `resource`, `amount`, `asset`, `chain`, `tx_hash`, `payer`, `resolved_by` |"
|
|
4492
|
+
text: "## Webhook events\n\nEnvelope: `{ id, event, created_at, data }`, signed with `X-Webhook-Signature-256`. Verification and retries: [Webhooks](/docs/md/webhooks.md).\n\n| Event | Fires when | `data` fields |\n| --- | --- | --- |\n| `checkout.session.created` | A session is created. | `session_id`, `amount_total`, `currency`, `late` |\n| `checkout.session.deposit_detected` | Funds seen on-chain, awaiting confirmations. | `session_id`, `amount_total`, `currency`, `late`, `tx_hash`, `amount_received`, `received_token` |\n| `checkout.session.paid` | Confirmed \u2014 safe to fulfil. `late: true` if it arrived after the deadline. | `session_id`, `amount_total`, `currency`, `late`, `tx_hash` |\n| `checkout.session.settled` | Funds swept to your payout wallet. | `session_id`, `amount_total`, `currency`, `late`, `sweep_tx_hash` |\n| `checkout.session.unresolved` | Underpaid, wrong token, or a confirmed deposit reverted by a reorg (`reason`). | `session_id`, `amount_total`, `currency`, `late`, `reason`, `amount_expected`, `amount_received`, `received_token`, `tx_hash` |\n| `checkout.session.expired` | Deadline passed without full payment. | `session_id`, `amount_total`, `currency`, `late`, `reason`, `expired_at`, `source`, `refund_tx_hash` |\n| `checkout.refund.recorded` | A refund was made on-chain (`kind`). | `session_id`, `kind`, `amount`, `token`, `tx_hash`, `refund_address` |\n| `x402.payment.resolved` | An x402 call that settled with `503` reached its outcome. Only fires for those \u2014 the normal path answers inline. | `payment_id`, `status`, `resource`, `amount`, `asset`, `chain`, `tx_hash`, `payer`, `resolved_by` |"
|
|
4241
4493
|
},
|
|
4242
4494
|
{
|
|
4243
4495
|
page: "errors",
|
|
@@ -4251,28 +4503,28 @@ var docs_data_default = {
|
|
|
4251
4503
|
pageTitle: "Errors",
|
|
4252
4504
|
heading: "Response format",
|
|
4253
4505
|
url: "https://bitpal.io/docs/#errors",
|
|
4254
|
-
text: '## Response format\n\nSuccessful responses are `{ "data": \u2026 }`. Failed responses use an HTTP status code and
|
|
4506
|
+
text: '## Response format\n\nSuccessful responses are `{ "data": \u2026 }`. Failed responses use an HTTP status code and this JSON body:\n\n```\n{\n "error": "checkout not enabled for USDC on eip155:8453", // human-readable\n "code": "FEATURE_NOT_ENABLED", // machine-readable\n "retryable": false,\n "trace_id": "4f1c\u2026" // quote it to support\n}\n```\n\n`error` is a human-readable message and may change \u2014 **branch on `code`** (after the HTTP status), never on the text of `error`. Validation failures add a `details` array. The SDK exposes the same fields:\n\n```\nimport { BitPalError } from \'@bitpal/checkout\';\n\ntry {\n await bitpal.checkout.createSession(params);\n} catch (err) {\n if (err instanceof BitPalError) {\n err.statusCode; // 400, 404, 429, \u2026\n err.code; // \'CHECKOUT_AMOUNT_TOO_LOW\' (when the API sent one)\n err.apiMessage; // the API\'s description\n err.retryable; // true / false / undefined\n err.traceId; // for support\n }\n throw err;\n}\n```'
|
|
4255
4507
|
},
|
|
4256
4508
|
{
|
|
4257
4509
|
page: "errors",
|
|
4258
4510
|
pageTitle: "Errors",
|
|
4259
4511
|
heading: "HTTP status codes",
|
|
4260
4512
|
url: "https://bitpal.io/docs/#errors",
|
|
4261
|
-
text: "## HTTP status codes\n\n| Status | Meaning | Retry? |\n| --- | --- | --- |\n| `400` | The request is invalid \u2014 fix it. | No |\n| `401` | Missing, invalid, revoked or expired API key, or a key for the other mode. | No |\n| `403` | Not allowed \u2014 for example the merchant account is frozen or suspended. | No |\n| `404` | The object doesn't exist in this environment (test and live are separate). | No |\n| `409` | Conflicts with the current state, or the same `Idempotency-Key` is still in progress. | After re-reading state |\n| `410` | Gone \u2014 the session or link has expired, sold out or been deactivated. | No |\n| `422` | `Idempotency-Key` reused with a different body. | No \u2014 use a new key |\n| `429` | Rate limited. Honour `Retry-After` (seconds). | Yes, with backoff |\n| `5xx` | BitPal-side problem. | Yes, with backoff (same `Idempotency-Key`
|
|
4513
|
+
text: "## HTTP status codes\n\n| Status | Meaning | Retry? |\n| --- | --- | --- |\n| `400` | The request is invalid \u2014 fix it. | No |\n| `401` | Missing, invalid, revoked or expired API key, or a key for the other mode. | No |\n| `403` | Not allowed \u2014 for example the merchant account is frozen or suspended. | No |\n| `404` | The object doesn't exist in this environment (test and live are separate). | No |\n| `409` | Conflicts with the current state, or the same `Idempotency-Key` is still in progress. | After re-reading state |\n| `410` | Gone \u2014 the session or link has expired, sold out or been deactivated. | No |\n| `422` | `Idempotency-Key` reused with a different body or on a different endpoint. | No \u2014 use a new key |\n| `429` | Rate limited. Honour `Retry-After` (seconds). | Yes, with backoff |\n| `5xx` | BitPal-side problem. | Yes, with backoff (same `Idempotency-Key`) |"
|
|
4262
4514
|
},
|
|
4263
4515
|
{
|
|
4264
4516
|
page: "errors",
|
|
4265
4517
|
pageTitle: "Errors",
|
|
4266
4518
|
heading: "Common error codes",
|
|
4267
4519
|
url: "https://bitpal.io/docs/#errors",
|
|
4268
|
-
text: "## Common error codes\n\n| Code | HTTP | Cause \u2192 fix |\n| --- | --- | --- |\n| `UNAUTHORIZED` | 401 | Bad or
|
|
4520
|
+
text: "## Common error codes\n\n| Code | HTTP | Cause \u2192 fix |\n| --- | --- | --- |\n| `UNAUTHORIZED` | 401 | Bad, missing, revoked or expired `Authorization: Bearer bp_\u2026` key. |\n| `VALIDATION_FAILED` | 400 | Body doesn't match the schema \u2014 see `details`. Typical: a line item with both or neither of `amount` / `amount_atomic`. |\n| `MIXED_LINE_ITEMS` | 400 | Raw line items and `price_id` items in one session. Use one kind. |\n| `CURRENCY_MIX_NOT_ALLOWED` | 400 | Line items in different currencies. Use one currency per session. |\n| `CHECKOUT_AMOUNT_TOO_LOW` | 400 | The total is below the minimum of 1.00. |\n| `UNSUPPORTED_CHAIN` \xB7 `UNSUPPORTED_ASSET` \xB7 `UNSUPPORTED_TOKEN` \xB7 `UNSUPPORTED_CURRENCY` | 400 | Unknown chain/token, or a chain not enabled for checkout. See [Chains & contracts](/docs/md/reference.md). |\n| `CHAIN_MODE_MISMATCH` | 400 | A mainnet CAIP-2 id with a test key, or a testnet id with a live key. Use the matching id, or a short name (`base`) that resolves to your key's mode. |\n| `ALLOWED_CHAINS_CONFLICT` | 400 | `pay_chain` and `allowed_pay_chains` were both set. Send one. |\n| `FEATURE_NOT_ENABLED` | 400 | That asset is not enabled for checkout on that chain. |\n| `SELLER_WALLET_NOT_CONFIGURED` | 400 | No payout wallet for that network family (EVM / Tron / Solana). Add one in Console \u2192 Settings \u2192 Payout Wallet. |\n| `INVALID_REFUND_ADDRESS` | 400 | The refund address is not valid for the chosen network. |\n| `ASSET_SELECTION_REQUIRED` | 400 | Multi-option session: pass `chain` and `token` when issuing the deposit address. |\n| `ASSET_NOT_OFFERED` | 400 | The `chain`/`token` you passed is not one of the session's `payment_options`. |\n| `CHECKOUT_BELOW_MIN_PAYMENT` | 400 | The amount is below the minimum payment for the chosen network (some networks, such as Tron, have a higher floor). Pick another option. |\n| `SESSION_NOT_FOUND` | 404 | Wrong id, or the session belongs to the other environment. |\n| `PRODUCT_NOT_FOUND` \xB7 `PAYMENT_LINK_NOT_FOUND` | 404 | Wrong id or environment. |\n| `PRICE_NOT_FOUND` | 404 / 400 | 404 when you fetch a price by id; 400 when a `price_id` in a session's line items or a payment link doesn't exist (wrong id or environment). |\n| `INVALID_STATE_TRANSITION` | 409 | The session can't do that in its current state \u2014 for example issuing a deposit address for a session that is already paid or expired. Re-read the session; create a new one if it has expired. |\n| `CHECKOUT_SESSION_EXPIRED` | 410 | A deposit address was requested after `expires_at` for a session that never got one. Create a new session. |\n| `LINK_EXPIRED` \xB7 `LINK_INACTIVE` \xB7 `LINK_SOLD_OUT` | 410 | The payment link can't take payments any more. |\n| `MERCHANT_FROZEN` \xB7 `MERCHANT_SUSPENDED` | 403 | The account is restricted. Contact support. |\n| `TOO_MANY_ACTIVE_SESSIONS` | 429 | Over 1,000 open sessions. Let unused ones expire, and don't create a session before the buyer intends to pay. |\n| `RATE_LIMIT_EXCEEDED` | 429 | Too many requests. Wait `Retry-After` seconds. |\n| `INVALID_IDEMPOTENCY_KEY` | 400 | `Idempotency-Key` must be 8\u2013128 characters. |\n| `IDEMPOTENCY_KEY_REUSED` | 422 | Same key with a different body, or on a different endpoint (method and path are part of the fingerprint). Use a new key for a new request. |\n| `IDEMPOTENCY_IN_PROGRESS` | 409 | The first request with this key is still running. Retry shortly. |\n| `INTERNAL_ERROR` | 500 | Retry with backoff, with the same `Idempotency-Key`; if it persists, contact support with `trace_id`. |"
|
|
4269
4521
|
},
|
|
4270
4522
|
{
|
|
4271
4523
|
page: "errors",
|
|
4272
4524
|
pageTitle: "Errors",
|
|
4273
4525
|
heading: "Retrying safely",
|
|
4274
4526
|
url: "https://bitpal.io/docs/#errors",
|
|
4275
|
-
text: "## Retrying safely\n\n- Send an `Idempotency-Key` (8\u2013128 characters, unique per operation \u2014 an order id works)
|
|
4527
|
+
text: "## Retrying safely\n\n- Send an `Idempotency-Key` (8\u2013128 characters, unique per operation \u2014 an order id works) on any merchant `POST` (sessions, products, prices, payment links, webhook endpoints, \u2026) \u2014 it is honoured on all of them. Replays within 24 hours return the original response with `X-Idempotency-Replayed: true`. A key is bound to one request: reusing it with a different body or on a different endpoint returns `422 IDEMPOTENCY_KEY_REUSED`. Failed requests (4xx/5xx) are not cached, so you can retry them with the same key.\n- Retry only `429`, `5xx` and network errors, with exponential backoff.\n- Rate-limited responses carry `Retry-After` and `X-RateLimit-Limit` / `-Remaining` / `-Reset`."
|
|
4276
4528
|
},
|
|
4277
4529
|
{
|
|
4278
4530
|
page: "flows",
|
|
@@ -4293,14 +4545,14 @@ var docs_data_default = {
|
|
|
4293
4545
|
pageTitle: "Popup & headless",
|
|
4294
4546
|
heading: "Popup",
|
|
4295
4547
|
url: "https://bitpal.io/docs/#flows",
|
|
4296
|
-
text:
|
|
4548
|
+
text: '## Popup\n\nBrowsers block `window.open` unless it runs directly in the click handler. Open an empty window first, then point it at the checkout URL once your backend has created the session.\n\n```\nasync function onPayClick() {\n // 1) open synchronously, inside the click handler\n const popup = window.open(\'\', \'bitpal_checkout\', \'width=460,height=720\');\n\n // 2) your backend creates the session (secret key stays on the server)\n const { checkout_url, session_id } = await fetch(\'/api/checkout\', { method: \'POST\' }).then((r) => r.json());\n\n // 3) navigate the popup \u2014 or fall back to a full redirect if it was blocked\n if (popup && !popup.closed) popup.location.href = checkout_url;\n else window.location.href = checkout_url;\n\n const origin = new URL(checkout_url).origin;\n window.addEventListener(\'message\', function onMessage(e) {\n if (e.origin !== origin) return; // trust only the checkout page\n const m = e.data;\n if (!m || m.source !== \'bitpal-checkout\' || m.type !== \'terminal\') return;\n window.removeEventListener(\'message\', onMessage);\n refreshOrderFromServer(session_id); // m.step: done | expired | error\n });\n}\n```\n\nWhen the payment reaches a final screen, the checkout page posts one message to its opener:\n\n```\n{ "source": "bitpal-checkout", "type": "terminal", "step": "done", "session_id": "cs_\u2026" }\n```\n\n- `step` is `done`, `expired` or `error`. One `terminal` message is sent per final screen.\n- If the buyer sends the wrong token, the page posts `{ "type": "status", "step": "wrong_token" }` instead. It is not final: the wrong token is returned and the session can still be paid with the right one, so keep listening for `terminal`.\n- The message is a **UI hint**. Re-read the order from your server (which learned the result from the webhook) before showing success.\n- Messages can be lost (the buyer closes the window, browser privacy settings). Keep a fallback: poll your own backend for the order status.\n- Don\'t close the popup yourself \u2014 the completion screen lets the buyer email a receipt. The popup closes itself only after the buyer sends that receipt; otherwise the buyer closes it with the **Close** button, so don\'t wait for the window to close.'
|
|
4297
4549
|
},
|
|
4298
4550
|
{
|
|
4299
4551
|
page: "flows",
|
|
4300
4552
|
pageTitle: "Popup & headless",
|
|
4301
4553
|
heading: "Headless (your own UI)",
|
|
4302
4554
|
url: "https://bitpal.io/docs/#flows",
|
|
4303
|
-
text: '## Headless (your own UI)\n\nUse this when you render the payment screen yourself. The buyer still pays to a per-order deposit address; you just display it.\n\n> **Call BitPal from your backend.** Browsers on your domain may only call the public session endpoint directly (CORS). Issue the deposit address and poll status through your server, and never ship a secret key to the browser.\n\n1. **Create the session** on your server (as in the Quickstart). Keep `id` and `session_token`.\n2. **Show the payment options.** `getPublicSession(id)` returns `payment_options` \u2014 one entry per allowed (chain, token) with `caip2`, `token_symbol`, `decimals` and the exact `amount_atomic`.\n3. **Collect a refund address** from the buyer, in the format of the chosen network (EVM `0x\u2026`, Tron `T\u2026`, Solana base58). It is committed into the deposit address and cannot change.\n4. **Issue the deposit address** (backend):\n\n ```\n const { data } = await bitpal.checkout.issueDepositAddress(session.id, {\n session_token: session.session_token,\n refundAddress: \'0xBuyerRefundWallet\u2026\',\n chain: \'eip155:84532\',\n token: \'USDC\',\n });\n // {\n // "sessionId": "cs_\u2026",\n // "chain": "eip155:84532",\n // "depositAddress": "0x6b19e871c934aB52775B6712e110f3e4b0626347",\n // "expectedToken": "0x943b94dacf1507f2658f1cfe103ac36ebc13ef65",\n // "expectedAmount": "29000000", // in the token\'s own decimals\n // "feeAmount": "435000",\n // "refundAddress": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",\n // "deadline": "
|
|
4555
|
+
text: '## Headless (your own UI)\n\nUse this when you render the payment screen yourself. The buyer still pays to a per-order deposit address; you just display it.\n\n> **Call BitPal from your backend.** Browsers on your domain may only call the public session endpoint directly (CORS). Issue the deposit address and poll status through your server, and never ship a secret key to the browser.\n\n1. **Create the session** on your server (as in the Quickstart). Keep `id` and `session_token`.\n2. **Show the payment options.** `getPublicSession(id)` returns `payment_options` \u2014 one entry per allowed (chain, token) with `caip2`, `token_symbol`, `decimals` and the exact `amount_atomic`.\n3. **Collect a refund address** from the buyer, in the format of the chosen network (EVM `0x\u2026`, Tron `T\u2026`, Solana base58). It is committed into the deposit address and cannot change.\n4. **Issue the deposit address** (backend):\n\n ```\n const { data } = await bitpal.checkout.issueDepositAddress(session.id, {\n session_token: session.session_token,\n refundAddress: \'0xBuyerRefundWallet\u2026\',\n chain: \'eip155:84532\',\n token: \'USDC\',\n });\n // {\n // "sessionId": "cs_\u2026",\n // "chain": "eip155:84532",\n // "depositAddress": "0x6b19e871c934aB52775B6712e110f3e4b0626347",\n // "expectedToken": "0x943b94dacf1507f2658f1cfe103ac36ebc13ef65",\n // "expectedAmount": "29000000", // in the token\'s own decimals\n // "feeAmount": "435000",\n // "refundAddress": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",\n // "deadline": "1793957308", // on-chain deadline (unix s) = expiresAt + 5400 (90-minute grace)\n // "expiresAt": "2026-11-06T07:58:28.776Z"\n // }\n ```\n5. **Display** the address, a QR code and the exact amount \u2014 format `expectedAmount` with the token\'s decimals (6 for USDC, 18 for BNB USDT and JPYC). Show the countdown to `expiresAt`.\n6. **Track status** through your backend (`getStatus(id)`) for the UI, and **fulfil from the webhook**.\n\nCalling `issueDepositAddress` again for the same session returns the same address **and keeps the first refund address** \u2014 a later call cannot change it. A deposit address belongs to one order; don\'t reuse it.'
|
|
4304
4556
|
},
|
|
4305
4557
|
{
|
|
4306
4558
|
page: "lifecycle",
|
|
@@ -4328,14 +4580,14 @@ var docs_data_default = {
|
|
|
4328
4580
|
pageTitle: "Payment lifecycle",
|
|
4329
4581
|
heading: "Payment window and late payments",
|
|
4330
4582
|
url: "https://bitpal.io/docs/#lifecycle",
|
|
4331
|
-
text: '## Payment window and late payments\n\n- The buyer has `expires_in_minutes` (1\u201360, default 15) from session creation to pay.\n- **A full payment that arrives after the deadline is accepted as a late payment** for up to 7 days: you get `checkout.session.paid` with `"late": true` and the funds settle normally. Decide whether to fulfil, or refund the buyer yourself.\n- A **partial** payment after the deadline is refunded automatically (refund kind `late_deposit`).\n- Funds sent more than 7 days after the deadline are not watched automatically. Contact support with the session id and transaction hash.'
|
|
4583
|
+
text: '## Payment window and late payments\n\n- The buyer has `expires_in_minutes` (1\u201360, default 15) from session creation to pay. That deadline is the session\'s `expires_at`.\n- `late` compares the **block time of the deposit transaction** with `expires_at`. A transfer sent in time is `"late": false` even if its confirmations finish after the deadline; `checkout.session.settled` carries the same value as `paid`.\n- Once a deposit address has been issued, BitPal keeps the session open for **90 minutes after `expires_at`** so transfers already in flight can confirm. Underpayment refunds and `checkout.session.expired` for an issued session happen at the end of that window.\n- **A full payment that arrives after the deadline is accepted as a late payment** for up to 7 days: you get `checkout.session.paid` with `"late": true` and the funds settle normally. Decide whether to fulfil, or refund the buyer yourself.\n- This only applies to a session with no earlier on-chain action. If a wrong token was already returned or an underpayment already refunded, the expired session is closed for good: later deposits to its address are not accepted as payment (contact support with the session id and transaction hash).\n- Acceptance ends 1 hour before the on-chain refund window opens (`expires_at` + 90 minutes + 7 days). A full payment that arrives after that cutoff is not accepted; it is refunded automatically once the window opens (refund kind `late_deposit`).\n- A **partial** payment after the deadline is refunded automatically (refund kind `late_deposit`).\n- Funds sent more than 7 days after the deadline are not watched automatically. Contact support with the session id and transaction hash.'
|
|
4332
4584
|
},
|
|
4333
4585
|
{
|
|
4334
4586
|
page: "lifecycle",
|
|
4335
4587
|
pageTitle: "Payment lifecycle",
|
|
4336
4588
|
heading: "Refunds",
|
|
4337
4589
|
url: "https://bitpal.io/docs/#lifecycle",
|
|
4338
|
-
text: "## Refunds\n\nBitPal never holds funds, so mismatched payments are returned on-chain to the buyer's refund address \u2014 automatically, with no action from you. Each refund fires `checkout.refund.recorded`.\n\n| `kind` | When | Session outcome |\n| --- | --- | --- |\n| `overpay_excess` | More than the amount arrived. | Paid; the exact amount settles, the excess is refunded. |\n| `underpay_expired` | Still short at the deadline. | Expired; everything received is refunded. |\n| `wrong_token` | A token other than the selected one was sent. | Unchanged; still payable with the right token. |\n| `late_deposit` | A partial payment arrived after the deadline. | Stays expired. |\n\nRefunding a completed, correct payment (for example a customer return) is up to you: send the funds from your payout wallet."
|
|
4590
|
+
text: "## Refunds\n\nBitPal never holds funds, so mismatched payments are returned on-chain to the buyer's refund address \u2014 automatically, with no action from you. Each refund fires `checkout.refund.recorded`.\n\n| `kind` | When | Session outcome |\n| --- | --- | --- |\n| `overpay_excess` | More than the amount arrived. | Paid; the exact amount settles, the excess is refunded. |\n| `underpay_expired` | Still short at the deadline. | Expired; everything received is refunded. |\n| `wrong_token` | A token other than the selected one was sent. | Unchanged; still payable with the right token. |\n| `late_deposit` | A partial payment arrived after the deadline, or a full payment arrived after the late-acceptance cutoff (see above). | Stays expired. |\n\nRefunding a completed, correct payment (for example a customer return) is up to you: send the funds from your payout wallet."
|
|
4339
4591
|
},
|
|
4340
4592
|
{
|
|
4341
4593
|
page: "links",
|
|
@@ -4419,7 +4671,7 @@ var docs_data_default = {
|
|
|
4419
4671
|
pageTitle: "Quickstart",
|
|
4420
4672
|
heading: "3. Receive the webhook",
|
|
4421
4673
|
url: "https://bitpal.io/docs/#quickstart",
|
|
4422
|
-
text: "## 3. Receive the webhook\n\nConsole \u2192 Developers \u2192 Webhooks \u2192 **Add Endpoint**. The URL must be publicly reachable over HTTPS (for local development use a tunnel such as ngrok or cloudflared). Copy the signing secret (`whsec_\u2026`) \u2014 it is shown once.\n\n```\nimport express from 'express';\nimport { verifyWebhookSignature, parseWebhookEvent, WEBHOOK_HEADERS } from '@bitpal/checkout';\n\nconst app = express();\n\napp.post('/webhooks/bitpal', express.raw({ type: 'application/json' }), async (req, res) => {\n const raw = req.body.toString('utf8'); // exact bytes BitPal signed\n const ok = await verifyWebhookSignature( // async \u2014 always await it\n raw,\n req.header(WEBHOOK_HEADERS.signature) ?? '',\n process.env.BITPAL_WEBHOOK_SECRET, // whsec_\u2026\n { timestamp: req.header(WEBHOOK_HEADERS.timestamp) ?? '' },\n );\n if (!ok) return res.status(400).send('invalid signature');\n\n const event = parseWebhookEvent(raw);\n if (await alreadyProcessed(event.id)) return res.sendStatus(200); // at-least-once delivery\n\n if (event.event === 'checkout.session.paid') {\n await fulfilOrder(event.data.session_id, { late: event.data.late === true });\n }\n await markProcessed(event.id);\n res.sendStatus(200); // any 2xx within 10 s = delivered\n});\n```\n\nFor one successful payment you receive, in order: `checkout.session.created` \u2192 `checkout.session.deposit_detected` \u2192 `checkout.session.paid` \u2192 `checkout.session.settled`. Use **Send Test** in the console to check your endpoint before paying."
|
|
4674
|
+
text: "## 3. Receive the webhook\n\nConsole \u2192 Developers \u2192 Webhooks \u2192 **Add Endpoint**. The URL must be publicly reachable over HTTPS (for local development use a tunnel such as ngrok or cloudflared). Copy the signing secret (`whsec_\u2026`) \u2014 it is shown once.\n\n```\nimport express from 'express';\nimport { verifyWebhookSignature, parseWebhookEvent, WEBHOOK_HEADERS } from '@bitpal/checkout';\n\nconst app = express();\n\napp.post('/webhooks/bitpal', express.raw({ type: 'application/json' }), async (req, res) => {\n const raw = req.body.toString('utf8'); // exact bytes BitPal signed\n const ok = await verifyWebhookSignature( // async \u2014 always await it\n raw,\n req.header(WEBHOOK_HEADERS.signature) ?? '',\n process.env.BITPAL_WEBHOOK_SECRET, // whsec_\u2026\n { timestamp: req.header(WEBHOOK_HEADERS.timestamp) ?? '' },\n );\n if (!ok) return res.status(400).send('invalid signature');\n\n const event = parseWebhookEvent(raw);\n if (await alreadyProcessed(event.id)) return res.sendStatus(200); // at-least-once delivery\n\n if (event.event === 'checkout.session.paid') {\n await fulfilOrder(event.data.session_id, { late: event.data.late === true });\n }\n await markProcessed(event.id);\n res.sendStatus(200); // any 2xx within 10 s = delivered\n});\n```\n\nFor one successful payment you receive, in order: `checkout.session.created` \u2192 `checkout.session.deposit_detected` \u2192 `checkout.session.paid` \u2192 `checkout.session.settled`. `deposit_detected` is not guaranteed: `paid` can arrive without it (a late payment, or a deposit that was already fully confirmed when BitPal first saw it), so never wait for it before fulfilling. Use **Send Test** in the console to check your endpoint before paying."
|
|
4423
4675
|
},
|
|
4424
4676
|
{
|
|
4425
4677
|
page: "quickstart",
|
|
@@ -4461,7 +4713,7 @@ var docs_data_default = {
|
|
|
4461
4713
|
pageTitle: "Chains & contracts",
|
|
4462
4714
|
heading: "Refunds & edge cases",
|
|
4463
4715
|
url: "https://bitpal.io/docs/#reference",
|
|
4464
|
-
text: "## Refunds & edge cases\n\nPayments are push transfers, so a buyer can pay the wrong amount or token. BitPal resolves each case automatically to the buyer's refund address (set at pay time, committed & immutable).\n\n| Case | What happens |\n| --- | --- |\n| **Overpaid** | Settles the exact amount; surplus refunded. |\n| **Underpaid** | Open for a top-up; refunded if the deadline passes. |\n| **Wrong token** | Returned to the buyer; order stays open. |\n| **Expired** | No funds arrived \u2014 address reclaimed. |\n| **Late, full amount** | Accepted as a late payment for up to 7 days (`late: true`); settles normally. |\n| **Late, partial** | Refunded (`late_deposit`). |"
|
|
4716
|
+
text: "## Refunds & edge cases\n\nPayments are push transfers, so a buyer can pay the wrong amount or token. BitPal resolves each case automatically to the buyer's refund address (set at pay time, committed & immutable).\n\n| Case | What happens |\n| --- | --- |\n| **Overpaid** | Settles the exact amount; surplus refunded. |\n| **Underpaid** | Open for a top-up; refunded if the deadline passes. |\n| **Wrong token** | Returned to the buyer; order stays open. |\n| **Expired** | No funds arrived \u2014 address reclaimed. |\n| **Late, full amount** | Accepted as a late payment for up to 7 days (`late: true`); settles normally. Not for sessions that already had a wrong-token return or underpayment refund; after the cutoff it is refunded (`late_deposit`). |\n| **Late, partial** | Refunded (`late_deposit`). |"
|
|
4465
4717
|
},
|
|
4466
4718
|
{
|
|
4467
4719
|
page: "reference",
|
|
@@ -4482,7 +4734,7 @@ var docs_data_default = {
|
|
|
4482
4734
|
pageTitle: "SDK",
|
|
4483
4735
|
heading: "Install & create a session",
|
|
4484
4736
|
url: "https://bitpal.io/docs/#sdk",
|
|
4485
|
-
text: "## Install & create a session\n\n```\nnpm install @bitpal/checkout\n```\n\n```\nimport { BitPal } from '@bitpal/checkout';\nconst bitpal = new BitPal(process.env.BITPAL_API_KEY); // bp_test_\u2026 or bp_live_\u2026\n\nconst { data: session } = await bitpal.checkout.createSession({\n line_items: [{ name: 'Pro plan', amount: '29', currency: 'USDC' }],\n pay_chain: '
|
|
4737
|
+
text: "## Install & create a session\n\n```\nnpm install @bitpal/checkout\n```\n\n```\nimport { BitPal } from '@bitpal/checkout';\nconst bitpal = new BitPal(process.env.BITPAL_API_KEY); // bp_test_\u2026 or bp_live_\u2026\n\n// A CAIP-2 id is absolute: 'eip155:8453' (Base mainnet) works only with a live key \u2014\n// with a test key it fails with CHAIN_MODE_MISMATCH. Use 'eip155:84532' for test, or a short name.\nconst { data: session } = await bitpal.checkout.createSession({\n line_items: [{ name: 'Pro plan', amount: '29', currency: 'USDC' }],\n pay_chain: 'base', // short name \u2192 Base Sepolia with bp_test_, Base with bp_live_\n expires_in_minutes: 30, // payment window 1\u201360, default 15\n success_url: 'https://your.store/thanks?s={CHECKOUT_SESSION_ID}',\n});\nredirect(session.checkout_url);\n```"
|
|
4486
4738
|
},
|
|
4487
4739
|
{
|
|
4488
4740
|
page: "sdk",
|
|
@@ -4503,7 +4755,7 @@ var docs_data_default = {
|
|
|
4503
4755
|
pageTitle: "SDK",
|
|
4504
4756
|
heading: "Example: poll status",
|
|
4505
4757
|
url: "https://bitpal.io/docs/#sdk",
|
|
4506
|
-
text: "## Example: poll status\n\n```\nconst { data } = await bitpal.checkout.getStatus(sessionId);\nif (data.status === 'paid_confirmed' || data.status === 'swept') showSuccess(); // UI only \u2014 fulfil from the webhook\n```\n\nStatus values: `created` \u2192 `awaiting_deposit` \u2192 `deposit_detected` \u2192 `paid_confirmed` \u2192 `swept`, plus `expired`, `unresolved_underpaid`, `
|
|
4758
|
+
text: "## Example: poll status\n\n```\nconst { data } = await bitpal.checkout.getStatus(sessionId);\nif (data.status === 'paid_confirmed' || data.status === 'swept') showSuccess(); // UI only \u2014 fulfil from the webhook\n```\n\nStatus values: `created` \u2192 `awaiting_deposit` \u2192 `deposit_detected` \u2192 `paid_confirmed` \u2192 `swept`, plus `expired`, `unresolved_underpaid`, `wrong_token`. (Sessions opened through the legacy wallet-connect flow may report `pending_payment`; treat it like `created`.) See [Payment lifecycle](/docs/md/lifecycle.md)."
|
|
4507
4759
|
},
|
|
4508
4760
|
{
|
|
4509
4761
|
page: "sdk",
|
|
@@ -4545,21 +4797,70 @@ var docs_data_default = {
|
|
|
4545
4797
|
pageTitle: "Webhooks",
|
|
4546
4798
|
heading: "Verifying the signature",
|
|
4547
4799
|
url: "https://bitpal.io/docs/#webhooks",
|
|
4548
|
-
text: '## Verifying the signature\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Reject the request if `X-Webhook-Timestamp` is more than 5 minutes from your clock (replay protection).\n3. Compute `HMAC-SHA256(key = your whsec_\u2026 secret, message = timestamp + "." + raw body)` as lowercase hex.\n4. Compare it with the part after `sha256=` using a constant-time comparison
|
|
4800
|
+
text: '## Verifying the signature\n\n1. Read the raw request body as bytes, before any JSON parsing.\n2. Reject the request if `X-Webhook-Timestamp` is more than 5 minutes from your clock (replay protection).\n3. Compute `HMAC-SHA256(key = your whsec_\u2026 secret, message = timestamp + "." + raw body)` as lowercase hex.\n4. Compare it with the part after `sha256=` using a constant-time comparison.'
|
|
4801
|
+
},
|
|
4802
|
+
{
|
|
4803
|
+
page: "webhooks",
|
|
4804
|
+
pageTitle: "Webhooks",
|
|
4805
|
+
heading: "Verifying the signature \u203A Python",
|
|
4806
|
+
url: "https://bitpal.io/docs/#webhooks",
|
|
4807
|
+
text: '### Python\n\n```\nimport hmac, hashlib, time\nfrom datetime import datetime\n\ndef verify_bitpal_webhook(raw_body: bytes, signature: str, timestamp: str, secret: str, tolerance_sec: int = 300) -> bool:\n if not signature.startswith("sha256=") or not timestamp:\n return False\n try:\n sent = datetime.fromisoformat(timestamp.replace("Z", "+00:00")).timestamp()\n except (ValueError, OverflowError, OSError):\n return False\n if abs(time.time() - sent) > tolerance_sec:\n return False\n expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()\n # compare bytes: compare_digest raises TypeError on non-ASCII str input\n return hmac.compare_digest(signature[len("sha256="):].encode(), expected.encode())\n\n# Flask: verify_bitpal_webhook(request.get_data(), request.headers.get("X-Webhook-Signature-256", ""),\n# request.headers.get("X-Webhook-Timestamp", ""), os.environ["BITPAL_WEBHOOK_SECRET"])\n```'
|
|
4808
|
+
},
|
|
4809
|
+
{
|
|
4810
|
+
page: "webhooks",
|
|
4811
|
+
pageTitle: "Webhooks",
|
|
4812
|
+
heading: "Verifying the signature \u203A Go",
|
|
4813
|
+
url: "https://bitpal.io/docs/#webhooks",
|
|
4814
|
+
text: '### Go\n\n```\nimport (\n "crypto/hmac"\n "crypto/sha256"\n "encoding/hex"\n "strings"\n "time"\n)\n\nfunc VerifyBitPalWebhook(rawBody []byte, signature, timestamp, secret string) bool {\n const prefix = "sha256="\n if !strings.HasPrefix(signature, prefix) || timestamp == "" {\n return false\n }\n sent, err := time.Parse(time.RFC3339Nano, timestamp)\n if err != nil {\n return false\n }\n if d := time.Since(sent); d > 5*time.Minute || d < -5*time.Minute {\n return false\n }\n mac := hmac.New(sha256.New, []byte(secret))\n mac.Write([]byte(timestamp + "."))\n mac.Write(rawBody)\n expected := hex.EncodeToString(mac.Sum(nil))\n return hmac.Equal([]byte(signature[len(prefix):]), []byte(expected))\n}\n```'
|
|
4815
|
+
},
|
|
4816
|
+
{
|
|
4817
|
+
page: "webhooks",
|
|
4818
|
+
pageTitle: "Webhooks",
|
|
4819
|
+
heading: "Verifying the signature \u203A PHP",
|
|
4820
|
+
url: "https://bitpal.io/docs/#webhooks",
|
|
4821
|
+
text: "### PHP\n\n```\nfunction verify_bitpal_webhook(string $rawBody, string $signature, string $timestamp, string $secret, int $toleranceSec = 300): bool {\n if (strpos($signature, 'sha256=') !== 0 || $timestamp === '') return false;\n $sent = strtotime($timestamp);\n if ($sent === false || abs(time() - $sent) > $toleranceSec) return false;\n $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);\n return hash_equals($expected, substr($signature, 7));\n}\n\n$secret = getenv('BITPAL_WEBHOOK_SECRET'); // false when unset\nif ($secret === false || $secret === '') {\n http_response_code(500); // misconfigured \u2014 never verify against an empty key\n exit;\n}\n$ok = verify_bitpal_webhook(\n (string) file_get_contents('php://input'),\n $_SERVER['HTTP_X_WEBHOOK_SIGNATURE_256'] ?? '',\n $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '',\n $secret\n);\n```"
|
|
4822
|
+
},
|
|
4823
|
+
{
|
|
4824
|
+
page: "webhooks",
|
|
4825
|
+
pageTitle: "Webhooks",
|
|
4826
|
+
heading: "Verifying the signature \u203A Java",
|
|
4827
|
+
url: "https://bitpal.io/docs/#webhooks",
|
|
4828
|
+
text: '### Java\n\n```\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\nimport java.time.DateTimeException;\nimport java.time.Duration;\nimport java.time.Instant;\n\npublic static boolean verifyBitPalWebhook(byte[] rawBody, String signature, String timestamp, String secret) throws Exception {\n if (signature == null || !signature.startsWith("sha256=") || timestamp == null || timestamp.isEmpty()) return false;\n Instant sent;\n try { sent = Instant.parse(timestamp); } catch (DateTimeException e) { return false; }\n if (Duration.between(sent, Instant.now()).abs().getSeconds() > 300) return false;\n Mac mac = Mac.getInstance("HmacSHA256");\n mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));\n mac.update((timestamp + ".").getBytes(StandardCharsets.UTF_8));\n byte[] digest = mac.doFinal(rawBody);\n StringBuilder hex = new StringBuilder();\n for (byte b : digest) hex.append(String.format("%02x", b));\n return MessageDigest.isEqual(hex.toString().getBytes(StandardCharsets.UTF_8),\n signature.substring(7).getBytes(StandardCharsets.UTF_8));\n}\n```'
|
|
4549
4829
|
},
|
|
4550
4830
|
{
|
|
4551
4831
|
page: "webhooks",
|
|
4552
4832
|
pageTitle: "Webhooks",
|
|
4553
4833
|
heading: "Event data",
|
|
4554
4834
|
url: "https://bitpal.io/docs/#webhooks",
|
|
4555
|
-
text: '## Event data\n\nEvery `data` object has `session_id`. Session events also carry `amount_total` (6-decimal integer string), `currency` and `late`. Token amounts are in the pay token\'s decimals \u2014 see [Payment lifecycle \u2192 Amounts](/docs/md/lifecycle.md).\n\n| Event | Extra fields in `data` |\n| --- | --- |\n| `checkout.session.created` | \u2014 (only `session_id`, `amount_total`, `currency`) |\n| `checkout.session.deposit_detected` | `tx_hash`, `amount_received`, `received_token` (token contract) |\n| `checkout.session.paid` | `tx_hash`, `late` |\n| `checkout.session.settled` | `sweep_tx_hash` |\n| `checkout.session.unresolved` | `reason`: `underpaid` (+ `amount_received`, `amount_expected`, `received_token`, `tx_hash`) \xB7 `wrong_token` (+ `received_token`) \xB7 `reorg` |\n| `checkout.session.expired` | `reason: "no_deposit"`, or `refund_tx_hash` when a partial payment was refunded |\n| `checkout.refund.recorded` | `kind` (`overpay_excess` \xB7 `underpay_expired` \xB7 `wrong_token` \xB7 `late_deposit`), `amount`, `token`, `tx_hash`, `refund_address`
|
|
4835
|
+
text: '## Event data\n\nEvery `data` object has `session_id`. Session events also carry `amount_total` (6-decimal integer string), `currency` and `late`. Token amounts are in the pay token\'s decimals \u2014 see [Payment lifecycle \u2192 Amounts](/docs/md/lifecycle.md).\n\n| Event | Extra fields in `data` |\n| --- | --- |\n| `checkout.session.created` | \u2014 (only `session_id`, `amount_total`, `currency`) |\n| `checkout.session.deposit_detected` | `tx_hash`, `amount_received`, `received_token` (token contract) |\n| `checkout.session.paid` | `tx_hash`, `late` |\n| `checkout.session.settled` | `sweep_tx_hash` |\n| `checkout.session.unresolved` | `reason`: `underpaid` (+ `amount_received`, `amount_expected`, `received_token`, `tx_hash`) \xB7 `wrong_token` (+ `received_token`) \xB7 `reorg` |\n| `checkout.session.expired` | `reason: "no_deposit"`, or `refund_tx_hash` when a partial payment was refunded |\n| `checkout.refund.recorded` | `kind` (`overpay_excess` \xB7 `underpay_expired` \xB7 `wrong_token` \xB7 `late_deposit`), `amount`, `token`, `tx_hash`, `refund_address` |'
|
|
4836
|
+
},
|
|
4837
|
+
{
|
|
4838
|
+
page: "webhooks",
|
|
4839
|
+
pageTitle: "Webhooks",
|
|
4840
|
+
heading: "Event data \u203A Example: `checkout.session.paid`",
|
|
4841
|
+
url: "https://bitpal.io/docs/#webhooks",
|
|
4842
|
+
text: '### Example: `checkout.session.paid`\n\n```\n{\n "id": "1ff63374-9417-4f87-850d-e869a3bd2dbe",\n "event": "checkout.session.paid",\n "created_at": "2026-11-06T07:20:49.549Z",\n "data": {\n "session_id": "cs_HFoj91aFFbHSRk1LN1-9K01q",\n "amount_total": "1000000",\n "currency": "USDC",\n "late": false,\n "tx_hash": "0xd9a75dfe753b20027a0a93841c4e70af1b6be061b59e530d1bc381e88c196fe3"\n }\n}\n```'
|
|
4843
|
+
},
|
|
4844
|
+
{
|
|
4845
|
+
page: "webhooks",
|
|
4846
|
+
pageTitle: "Webhooks",
|
|
4847
|
+
heading: "Event data \u203A Example: `checkout.session.unresolved` (underpaid)",
|
|
4848
|
+
url: "https://bitpal.io/docs/#webhooks",
|
|
4849
|
+
text: '### Example: `checkout.session.unresolved` (underpaid)\n\n```\n{\n "id": "18d80275-45be-43d7-a57b-4b5038bd2736",\n "event": "checkout.session.unresolved",\n "created_at": "2026-11-06T07:14:19.252Z",\n "data": {\n "session_id": "cs_1ATJ7V5QknzeFpTNPMU_449s",\n "amount_total": "5000000",\n "currency": "USDC",\n "late": false,\n "reason": "underpaid",\n "amount_expected": "5000000",\n "amount_received": "2500000",\n "received_token": "0x943b94Dacf1507F2658f1CfE103Ac36Ebc13Ef65",\n "tx_hash": "0x4461291b5d743d3950a474c015098b8cb53b4373346bd510f74481dd8dbb9d65"\n }\n}\n```'
|
|
4850
|
+
},
|
|
4851
|
+
{
|
|
4852
|
+
page: "webhooks",
|
|
4853
|
+
pageTitle: "Webhooks",
|
|
4854
|
+
heading: "Event data \u203A Example: `checkout.refund.recorded`",
|
|
4855
|
+
url: "https://bitpal.io/docs/#webhooks",
|
|
4856
|
+
text: '### Example: `checkout.refund.recorded`\n\n```\n{\n "id": "f2fdcce6-1880-44b2-8351-906c42b7c4cd",\n "event": "checkout.refund.recorded",\n "created_at": "2026-11-06T07:20:29.547Z",\n "data": {\n "session_id": "cs_YsrZGcZzVsVHJo8ZNVmsTL1D",\n "kind": "overpay_excess",\n "amount": "1000000",\n "token": "0x943b94Dacf1507F2658f1CfE103Ac36Ebc13Ef65",\n "tx_hash": "0xe943412e2be8b3e3fc6359b47a8eea0cc4de17768b069f52ebab1aacd873aec4",\n "refund_address": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"\n }\n}\n```'
|
|
4556
4857
|
},
|
|
4557
4858
|
{
|
|
4558
4859
|
page: "webhooks",
|
|
4559
4860
|
pageTitle: "Webhooks",
|
|
4560
4861
|
heading: "Delivery, retries and ordering",
|
|
4561
4862
|
url: "https://bitpal.io/docs/#webhooks",
|
|
4562
|
-
text: "## Delivery, retries and ordering\n\n- Respond with any **2xx within 10 seconds**. Do slow work after responding (queue it).\n-
|
|
4863
|
+
text: "## Delivery, retries and ordering\n\n- Respond with any **2xx within 10 seconds**. Do slow work after responding (queue it).\n- A failed delivery (non-2xx, timeout or connection error) is retried right away twice (after 1 s and 2 s), then after **1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h** \u2014 10 attempts over about a day. After the last one the delivery moves to the **dead-letter queue**; resend it from Console \u2192 Developers \u2192 Webhooks once your endpoint is fixed (dead-lettered deliveries can be resent for 30 days).\n- Delivery is **at least once**: the same event can arrive more than once. Deduplicate on the body `id` and store it in the same transaction as your side effect.\n- Order is not guaranteed \u2014 a retried `paid` can arrive after `settled`. Keep the furthest state you have seen instead of overwriting it with an older one. Not every event is sent for every payment: `paid` can arrive with no `deposit_detected` before it (a late payment, or a deposit already confirmed when it was first seen).\n- Test and live are separate: each environment has its own endpoints and secrets."
|
|
4563
4864
|
},
|
|
4564
4865
|
{
|
|
4565
4866
|
page: "webhooks",
|
|
@@ -4587,7 +4888,7 @@ var docs_data_default = {
|
|
|
4587
4888
|
pageTitle: "x402",
|
|
4588
4889
|
heading: "Add it to a route",
|
|
4589
4890
|
url: "https://bitpal.io/docs/#x402",
|
|
4590
|
-
text: "## Add it to a route\n\n```\nnpm install @bitpal/checkout\n```\n\n```\nimport { x402 } from '@bitpal/checkout';\n\n// $1.00 USDC per call\napp.get('/report', x402({\n apiKey: process.env.BITPAL_API_KEY, // bp_test_\u2026 or bp_live_\u2026\n payTo: '0xYourPayoutWallet'
|
|
4891
|
+
text: "## Add it to a route\n\n```\nnpm install @bitpal/checkout\n```\n\n```\nimport { x402 } from '@bitpal/checkout';\n\n// $1.00 USDC per call\napp.get('/report', x402({\n apiKey: process.env.BITPAL_API_KEY, // bp_test_\u2026 or bp_live_\u2026\n payTo: '0xYourPayoutWallet', // the payout wallet registered in Console for this mode \u2014 anything else: PAYTO_NOT_MERCHANT_WALLET\n network: 'base', // test key \u2192 'base-sepolia'\n amount: '1.00', // \uC0AC\uB78C \uD45C\uAE30 \u2014 decimals \uACC4\uC0B0 \uBD88\uD544\uC694\n}), (req, res) => {\n res.json({ report: '\u2026' });\n});\n```"
|
|
4591
4892
|
},
|
|
4592
4893
|
{
|
|
4593
4894
|
page: "x402",
|
|
@@ -4631,7 +4932,7 @@ var docs_data_default = {
|
|
|
4631
4932
|
"UNAUTHORIZED"
|
|
4632
4933
|
],
|
|
4633
4934
|
http: "401",
|
|
4634
|
-
description: "Bad or
|
|
4935
|
+
description: "Bad, missing, revoked or expired `Authorization: Bearer bp_\u2026` key."
|
|
4635
4936
|
},
|
|
4636
4937
|
{
|
|
4637
4938
|
codes: [
|
|
@@ -4669,7 +4970,21 @@ var docs_data_default = {
|
|
|
4669
4970
|
"UNSUPPORTED_CURRENCY"
|
|
4670
4971
|
],
|
|
4671
4972
|
http: "400",
|
|
4672
|
-
description: "Unknown chain/token, or a
|
|
4973
|
+
description: "Unknown chain/token, or a chain not enabled for checkout. See [Chains & contracts](/docs/md/reference.md)."
|
|
4974
|
+
},
|
|
4975
|
+
{
|
|
4976
|
+
codes: [
|
|
4977
|
+
"CHAIN_MODE_MISMATCH"
|
|
4978
|
+
],
|
|
4979
|
+
http: "400",
|
|
4980
|
+
description: "A mainnet CAIP-2 id with a test key, or a testnet id with a live key. Use the matching id, or a short name (`base`) that resolves to your key's mode."
|
|
4981
|
+
},
|
|
4982
|
+
{
|
|
4983
|
+
codes: [
|
|
4984
|
+
"ALLOWED_CHAINS_CONFLICT"
|
|
4985
|
+
],
|
|
4986
|
+
http: "400",
|
|
4987
|
+
description: "`pay_chain` and `allowed_pay_chains` were both set. Send one."
|
|
4673
4988
|
},
|
|
4674
4989
|
{
|
|
4675
4990
|
codes: [
|
|
@@ -4694,11 +5009,25 @@ var docs_data_default = {
|
|
|
4694
5009
|
},
|
|
4695
5010
|
{
|
|
4696
5011
|
codes: [
|
|
4697
|
-
"
|
|
5012
|
+
"ASSET_SELECTION_REQUIRED"
|
|
4698
5013
|
],
|
|
4699
5014
|
http: "400",
|
|
4700
5015
|
description: "Multi-option session: pass `chain` and `token` when issuing the deposit address."
|
|
4701
5016
|
},
|
|
5017
|
+
{
|
|
5018
|
+
codes: [
|
|
5019
|
+
"ASSET_NOT_OFFERED"
|
|
5020
|
+
],
|
|
5021
|
+
http: "400",
|
|
5022
|
+
description: "The `chain`/`token` you passed is not one of the session's `payment_options`."
|
|
5023
|
+
},
|
|
5024
|
+
{
|
|
5025
|
+
codes: [
|
|
5026
|
+
"CHECKOUT_BELOW_MIN_PAYMENT"
|
|
5027
|
+
],
|
|
5028
|
+
http: "400",
|
|
5029
|
+
description: "The amount is below the minimum payment for the chosen network (some networks, such as Tron, have a higher floor). Pick another option."
|
|
5030
|
+
},
|
|
4702
5031
|
{
|
|
4703
5032
|
codes: [
|
|
4704
5033
|
"SESSION_NOT_FOUND"
|
|
@@ -4708,7 +5037,6 @@ var docs_data_default = {
|
|
|
4708
5037
|
},
|
|
4709
5038
|
{
|
|
4710
5039
|
codes: [
|
|
4711
|
-
"PRICE_NOT_FOUND",
|
|
4712
5040
|
"PRODUCT_NOT_FOUND",
|
|
4713
5041
|
"PAYMENT_LINK_NOT_FOUND"
|
|
4714
5042
|
],
|
|
@@ -4717,17 +5045,24 @@ var docs_data_default = {
|
|
|
4717
5045
|
},
|
|
4718
5046
|
{
|
|
4719
5047
|
codes: [
|
|
4720
|
-
"
|
|
5048
|
+
"PRICE_NOT_FOUND"
|
|
5049
|
+
],
|
|
5050
|
+
http: "404 / 400",
|
|
5051
|
+
description: "404 when you fetch a price by id; 400 when a `price_id` in a session's line items or a payment link doesn't exist (wrong id or environment)."
|
|
5052
|
+
},
|
|
5053
|
+
{
|
|
5054
|
+
codes: [
|
|
5055
|
+
"INVALID_STATE_TRANSITION"
|
|
4721
5056
|
],
|
|
4722
5057
|
http: "409",
|
|
4723
|
-
description: "The session can
|
|
5058
|
+
description: "The session can't do that in its current state \u2014 for example issuing a deposit address for a session that is already paid or expired. Re-read the session; create a new one if it has expired."
|
|
4724
5059
|
},
|
|
4725
5060
|
{
|
|
4726
5061
|
codes: [
|
|
4727
5062
|
"CHECKOUT_SESSION_EXPIRED"
|
|
4728
5063
|
],
|
|
4729
5064
|
http: "410",
|
|
4730
|
-
description: "
|
|
5065
|
+
description: "A deposit address was requested after `expires_at` for a session that never got one. Create a new session."
|
|
4731
5066
|
},
|
|
4732
5067
|
{
|
|
4733
5068
|
codes: [
|
|
@@ -4772,7 +5107,7 @@ var docs_data_default = {
|
|
|
4772
5107
|
"IDEMPOTENCY_KEY_REUSED"
|
|
4773
5108
|
],
|
|
4774
5109
|
http: "422",
|
|
4775
|
-
description: "Same key, different
|
|
5110
|
+
description: "Same key with a different body, or on a different endpoint (method and path are part of the fingerprint). Use a new key for a new request."
|
|
4776
5111
|
},
|
|
4777
5112
|
{
|
|
4778
5113
|
codes: [
|
|
@@ -4786,7 +5121,7 @@ var docs_data_default = {
|
|
|
4786
5121
|
"INTERNAL_ERROR"
|
|
4787
5122
|
],
|
|
4788
5123
|
http: "500",
|
|
4789
|
-
description: "Retry with backoff
|
|
5124
|
+
description: "Retry with backoff, with the same `Idempotency-Key`; if it persists, contact support with `trace_id`."
|
|
4790
5125
|
}
|
|
4791
5126
|
],
|
|
4792
5127
|
openapi: {
|
|
@@ -5072,10 +5407,14 @@ var docs_data_default = {
|
|
|
5072
5407
|
tags: [
|
|
5073
5408
|
"Checkout sessions"
|
|
5074
5409
|
],
|
|
5075
|
-
summary: "Get session status
|
|
5076
|
-
description: "Lightweight status for polling.
|
|
5077
|
-
security: [
|
|
5078
|
-
|
|
5410
|
+
summary: "Get session status",
|
|
5411
|
+
description: "Lightweight status for polling from your server. Requires your API key (N13 \u2014 it is not a public endpoint). Buyer-facing pages should use `GET /v1/checkout/sessions/{id}/public`.",
|
|
5412
|
+
security: [
|
|
5413
|
+
{
|
|
5414
|
+
apiKey: []
|
|
5415
|
+
}
|
|
5416
|
+
],
|
|
5417
|
+
"x-bitpal-auth": "apiKey",
|
|
5079
5418
|
parameters: [
|
|
5080
5419
|
{
|
|
5081
5420
|
name: "id",
|
|
@@ -5259,7 +5598,8 @@ var docs_data_default = {
|
|
|
5259
5598
|
feeAmount: "435000",
|
|
5260
5599
|
refundAddress: "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",
|
|
5261
5600
|
deadline: "1793951938",
|
|
5262
|
-
expiresAt: "2026-11-06T07:58:28.776Z"
|
|
5601
|
+
expiresAt: "2026-11-06T07:58:28.776Z",
|
|
5602
|
+
depositOwner: null
|
|
5263
5603
|
}
|
|
5264
5604
|
}
|
|
5265
5605
|
}
|
|
@@ -5434,15 +5774,7 @@ var docs_data_default = {
|
|
|
5434
5774
|
"x-bitpal-auth": "apiKey",
|
|
5435
5775
|
parameters: [
|
|
5436
5776
|
{
|
|
5437
|
-
|
|
5438
|
-
in: "header",
|
|
5439
|
-
required: false,
|
|
5440
|
-
description: "Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key.",
|
|
5441
|
-
schema: {
|
|
5442
|
-
type: "string",
|
|
5443
|
-
minLength: 8,
|
|
5444
|
-
maxLength: 128
|
|
5445
|
-
}
|
|
5777
|
+
$ref: "#/components/parameters/IdempotencyKey"
|
|
5446
5778
|
}
|
|
5447
5779
|
],
|
|
5448
5780
|
requestBody: {
|
|
@@ -5728,15 +6060,7 @@ var docs_data_default = {
|
|
|
5728
6060
|
}
|
|
5729
6061
|
},
|
|
5730
6062
|
{
|
|
5731
|
-
|
|
5732
|
-
in: "header",
|
|
5733
|
-
required: false,
|
|
5734
|
-
description: "Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key.",
|
|
5735
|
-
schema: {
|
|
5736
|
-
type: "string",
|
|
5737
|
-
minLength: 8,
|
|
5738
|
-
maxLength: 128
|
|
5739
|
-
}
|
|
6063
|
+
$ref: "#/components/parameters/IdempotencyKey"
|
|
5740
6064
|
}
|
|
5741
6065
|
],
|
|
5742
6066
|
requestBody: {
|
|
@@ -6015,15 +6339,7 @@ var docs_data_default = {
|
|
|
6015
6339
|
"x-bitpal-auth": "apiKey",
|
|
6016
6340
|
parameters: [
|
|
6017
6341
|
{
|
|
6018
|
-
|
|
6019
|
-
in: "header",
|
|
6020
|
-
required: false,
|
|
6021
|
-
description: "Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key.",
|
|
6022
|
-
schema: {
|
|
6023
|
-
type: "string",
|
|
6024
|
-
minLength: 8,
|
|
6025
|
-
maxLength: 128
|
|
6026
|
-
}
|
|
6342
|
+
$ref: "#/components/parameters/IdempotencyKey"
|
|
6027
6343
|
}
|
|
6028
6344
|
],
|
|
6029
6345
|
requestBody: {
|
|
@@ -6349,15 +6665,7 @@ var docs_data_default = {
|
|
|
6349
6665
|
"x-bitpal-auth": "apiKey",
|
|
6350
6666
|
parameters: [
|
|
6351
6667
|
{
|
|
6352
|
-
|
|
6353
|
-
in: "header",
|
|
6354
|
-
required: false,
|
|
6355
|
-
description: "Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key.",
|
|
6356
|
-
schema: {
|
|
6357
|
-
type: "string",
|
|
6358
|
-
minLength: 8,
|
|
6359
|
-
maxLength: 128
|
|
6360
|
-
}
|
|
6668
|
+
$ref: "#/components/parameters/IdempotencyKey"
|
|
6361
6669
|
}
|
|
6362
6670
|
],
|
|
6363
6671
|
requestBody: {
|
|
@@ -6426,15 +6734,7 @@ var docs_data_default = {
|
|
|
6426
6734
|
"x-bitpal-auth": "apiKey",
|
|
6427
6735
|
parameters: [
|
|
6428
6736
|
{
|
|
6429
|
-
|
|
6430
|
-
in: "header",
|
|
6431
|
-
required: false,
|
|
6432
|
-
description: "Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key.",
|
|
6433
|
-
schema: {
|
|
6434
|
-
type: "string",
|
|
6435
|
-
minLength: 8,
|
|
6436
|
-
maxLength: 128
|
|
6437
|
-
}
|
|
6737
|
+
$ref: "#/components/parameters/IdempotencyKey"
|
|
6438
6738
|
}
|
|
6439
6739
|
],
|
|
6440
6740
|
requestBody: {
|
|
@@ -6501,15 +6801,7 @@ var docs_data_default = {
|
|
|
6501
6801
|
"x-bitpal-auth": "apiKey",
|
|
6502
6802
|
parameters: [
|
|
6503
6803
|
{
|
|
6504
|
-
|
|
6505
|
-
in: "header",
|
|
6506
|
-
required: false,
|
|
6507
|
-
description: "Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key.",
|
|
6508
|
-
schema: {
|
|
6509
|
-
type: "string",
|
|
6510
|
-
minLength: 8,
|
|
6511
|
-
maxLength: 128
|
|
6512
|
-
}
|
|
6804
|
+
$ref: "#/components/parameters/IdempotencyKey"
|
|
6513
6805
|
}
|
|
6514
6806
|
],
|
|
6515
6807
|
requestBody: {
|
|
@@ -6707,24 +6999,16 @@ var docs_data_default = {
|
|
|
6707
6999
|
"Webhook endpoints"
|
|
6708
7000
|
],
|
|
6709
7001
|
summary: "Create a webhook endpoint",
|
|
6710
|
-
description: "
|
|
7002
|
+
description: "**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403.\n\nThe response carries the `whsec_\u2026` signing secret \u2014 it is shown only once.",
|
|
6711
7003
|
security: [
|
|
6712
7004
|
{
|
|
6713
|
-
|
|
7005
|
+
consoleSession: []
|
|
6714
7006
|
}
|
|
6715
7007
|
],
|
|
6716
|
-
"x-bitpal-auth": "
|
|
7008
|
+
"x-bitpal-auth": "console",
|
|
6717
7009
|
parameters: [
|
|
6718
7010
|
{
|
|
6719
|
-
|
|
6720
|
-
in: "header",
|
|
6721
|
-
required: false,
|
|
6722
|
-
description: "Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key.",
|
|
6723
|
-
schema: {
|
|
6724
|
-
type: "string",
|
|
6725
|
-
minLength: 8,
|
|
6726
|
-
maxLength: 128
|
|
6727
|
-
}
|
|
7011
|
+
$ref: "#/components/parameters/IdempotencyKey"
|
|
6728
7012
|
}
|
|
6729
7013
|
],
|
|
6730
7014
|
requestBody: {
|
|
@@ -6780,13 +7064,13 @@ var docs_data_default = {
|
|
|
6780
7064
|
"Webhook endpoints"
|
|
6781
7065
|
],
|
|
6782
7066
|
summary: "List webhook endpoints",
|
|
6783
|
-
description: "
|
|
7067
|
+
description: "**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403.\n\nEndpoints of the API key\u2019s environment and mode. Note: list items use camelCase keys.",
|
|
6784
7068
|
security: [
|
|
6785
7069
|
{
|
|
6786
|
-
|
|
7070
|
+
consoleSession: []
|
|
6787
7071
|
}
|
|
6788
7072
|
],
|
|
6789
|
-
"x-bitpal-auth": "
|
|
7073
|
+
"x-bitpal-auth": "console",
|
|
6790
7074
|
parameters: [
|
|
6791
7075
|
{
|
|
6792
7076
|
name: "limit",
|
|
@@ -6794,7 +7078,8 @@ var docs_data_default = {
|
|
|
6794
7078
|
required: false,
|
|
6795
7079
|
schema: {
|
|
6796
7080
|
type: "integer",
|
|
6797
|
-
|
|
7081
|
+
minimum: 1,
|
|
7082
|
+
maximum: 100,
|
|
6798
7083
|
default: 50
|
|
6799
7084
|
}
|
|
6800
7085
|
},
|
|
@@ -6804,13 +7089,14 @@ var docs_data_default = {
|
|
|
6804
7089
|
required: false,
|
|
6805
7090
|
schema: {
|
|
6806
7091
|
type: "integer",
|
|
7092
|
+
minimum: 0,
|
|
6807
7093
|
default: 0
|
|
6808
7094
|
}
|
|
6809
7095
|
}
|
|
6810
7096
|
],
|
|
6811
7097
|
responses: {
|
|
6812
7098
|
"200": {
|
|
6813
|
-
description: "Endpoints.",
|
|
7099
|
+
description: "Endpoints, newest first. `meta` carries total/limit/offset.",
|
|
6814
7100
|
content: {
|
|
6815
7101
|
"application/json": {
|
|
6816
7102
|
schema: {
|
|
@@ -6822,47 +7108,13 @@ var docs_data_default = {
|
|
|
6822
7108
|
$ref: "#/components/schemas/WebhookEndpointListItem"
|
|
6823
7109
|
}
|
|
6824
7110
|
},
|
|
6825
|
-
pagination: {
|
|
6826
|
-
type: "object",
|
|
6827
|
-
properties: {
|
|
6828
|
-
limit: {
|
|
6829
|
-
type: "integer"
|
|
6830
|
-
},
|
|
6831
|
-
offset: {
|
|
6832
|
-
type: "integer"
|
|
6833
|
-
}
|
|
6834
|
-
},
|
|
6835
|
-
required: [
|
|
6836
|
-
"limit",
|
|
6837
|
-
"offset"
|
|
6838
|
-
]
|
|
6839
|
-
},
|
|
6840
7111
|
meta: {
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
mode: {
|
|
6844
|
-
type: "string",
|
|
6845
|
-
enum: [
|
|
6846
|
-
"TEST",
|
|
6847
|
-
"LIVE"
|
|
6848
|
-
]
|
|
6849
|
-
},
|
|
6850
|
-
requestId: {
|
|
6851
|
-
type: [
|
|
6852
|
-
"string",
|
|
6853
|
-
"null"
|
|
6854
|
-
]
|
|
6855
|
-
}
|
|
6856
|
-
},
|
|
6857
|
-
required: [
|
|
6858
|
-
"mode",
|
|
6859
|
-
"requestId"
|
|
6860
|
-
]
|
|
7112
|
+
$ref: "#/components/schemas/ResponseMeta",
|
|
7113
|
+
description: "Added to responses of authenticated endpoints. List endpoints may also carry total/limit/offset."
|
|
6861
7114
|
}
|
|
6862
7115
|
},
|
|
6863
7116
|
required: [
|
|
6864
|
-
"data"
|
|
6865
|
-
"pagination"
|
|
7117
|
+
"data"
|
|
6866
7118
|
]
|
|
6867
7119
|
}
|
|
6868
7120
|
}
|
|
@@ -6881,12 +7133,13 @@ var docs_data_default = {
|
|
|
6881
7133
|
"Webhook endpoints"
|
|
6882
7134
|
],
|
|
6883
7135
|
summary: "Update a webhook endpoint",
|
|
7136
|
+
description: "**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403.\n\nSetting `status` to `disabled` removes the endpoint and requires `reason_code` (otherwise 400 `REASON_CODE_REQUIRED`). It is recorded in the audit log.",
|
|
6884
7137
|
security: [
|
|
6885
7138
|
{
|
|
6886
|
-
|
|
7139
|
+
consoleSession: []
|
|
6887
7140
|
}
|
|
6888
7141
|
],
|
|
6889
|
-
"x-bitpal-auth": "
|
|
7142
|
+
"x-bitpal-auth": "console",
|
|
6890
7143
|
parameters: [
|
|
6891
7144
|
{
|
|
6892
7145
|
name: "id",
|
|
@@ -6950,13 +7203,13 @@ var docs_data_default = {
|
|
|
6950
7203
|
"Webhook endpoints"
|
|
6951
7204
|
],
|
|
6952
7205
|
summary: "Rotate the signing secret",
|
|
6953
|
-
description: "
|
|
7206
|
+
description: "**Console only.** Manage this from Console \u2192 Developers \u2192 Webhooks; calls with an API key return 403.\n\nTakes effect immediately. The new secret is returned once \u2014 update your verifier. Requires `reason_code` (400 `REASON_CODE_REQUIRED` if missing); recorded in the audit log.",
|
|
6954
7207
|
security: [
|
|
6955
7208
|
{
|
|
6956
|
-
|
|
7209
|
+
consoleSession: []
|
|
6957
7210
|
}
|
|
6958
7211
|
],
|
|
6959
|
-
"x-bitpal-auth": "
|
|
7212
|
+
"x-bitpal-auth": "console",
|
|
6960
7213
|
parameters: [
|
|
6961
7214
|
{
|
|
6962
7215
|
name: "id",
|
|
@@ -6968,17 +7221,22 @@ var docs_data_default = {
|
|
|
6968
7221
|
}
|
|
6969
7222
|
},
|
|
6970
7223
|
{
|
|
6971
|
-
|
|
6972
|
-
in: "header",
|
|
6973
|
-
required: false,
|
|
6974
|
-
description: "Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key.",
|
|
6975
|
-
schema: {
|
|
6976
|
-
type: "string",
|
|
6977
|
-
minLength: 8,
|
|
6978
|
-
maxLength: 128
|
|
6979
|
-
}
|
|
7224
|
+
$ref: "#/components/parameters/IdempotencyKey"
|
|
6980
7225
|
}
|
|
6981
7226
|
],
|
|
7227
|
+
requestBody: {
|
|
7228
|
+
required: true,
|
|
7229
|
+
content: {
|
|
7230
|
+
"application/json": {
|
|
7231
|
+
schema: {
|
|
7232
|
+
$ref: "#/components/schemas/RotateWebhookSecretRequest"
|
|
7233
|
+
},
|
|
7234
|
+
example: {
|
|
7235
|
+
reason_code: "rotation"
|
|
7236
|
+
}
|
|
7237
|
+
}
|
|
7238
|
+
}
|
|
7239
|
+
},
|
|
6982
7240
|
responses: {
|
|
6983
7241
|
"200": {
|
|
6984
7242
|
description: "Endpoint with the new secret.",
|
|
@@ -7002,6 +7260,9 @@ var docs_data_default = {
|
|
|
7002
7260
|
}
|
|
7003
7261
|
}
|
|
7004
7262
|
},
|
|
7263
|
+
"400": {
|
|
7264
|
+
$ref: "#/components/responses/Error400"
|
|
7265
|
+
},
|
|
7005
7266
|
"401": {
|
|
7006
7267
|
$ref: "#/components/responses/Error401"
|
|
7007
7268
|
},
|
|
@@ -7027,15 +7288,7 @@ var docs_data_default = {
|
|
|
7027
7288
|
"x-bitpal-auth": "apiKey",
|
|
7028
7289
|
parameters: [
|
|
7029
7290
|
{
|
|
7030
|
-
|
|
7031
|
-
in: "header",
|
|
7032
|
-
required: false,
|
|
7033
|
-
description: "Accepted but not yet enforced on this endpoint \u2014 retries are not de-duplicated by key.",
|
|
7034
|
-
schema: {
|
|
7035
|
-
type: "string",
|
|
7036
|
-
minLength: 8,
|
|
7037
|
-
maxLength: 128
|
|
7038
|
-
}
|
|
7291
|
+
$ref: "#/components/parameters/IdempotencyKey"
|
|
7039
7292
|
}
|
|
7040
7293
|
],
|
|
7041
7294
|
requestBody: {
|
|
@@ -7901,6 +8154,11 @@ var docs_data_default = {
|
|
|
7901
8154
|
scheme: "bearer",
|
|
7902
8155
|
bearerFormat: "bp_test_\u2026 / bp_live_\u2026",
|
|
7903
8156
|
description: "Secret API key from Console \u2192 Developers \u2192 API Keys. Server-side only. The prefix selects test or live mode."
|
|
8157
|
+
},
|
|
8158
|
+
consoleSession: {
|
|
8159
|
+
type: "http",
|
|
8160
|
+
scheme: "bearer",
|
|
8161
|
+
description: "BitPal Console login session. Not available to API keys \u2014 these operations are done in the Console."
|
|
7904
8162
|
}
|
|
7905
8163
|
},
|
|
7906
8164
|
parameters: {
|
|
@@ -8091,6 +8349,19 @@ var docs_data_default = {
|
|
|
8091
8349
|
"null"
|
|
8092
8350
|
]
|
|
8093
8351
|
},
|
|
8352
|
+
token_decimals: {
|
|
8353
|
+
type: [
|
|
8354
|
+
"integer",
|
|
8355
|
+
"null"
|
|
8356
|
+
],
|
|
8357
|
+
description: "Decimals of the payment token (USDC/USDT = 6, BSC-USD = 18). `amount_received` is in these units; `amount_total` is always 6-decimal."
|
|
8358
|
+
},
|
|
8359
|
+
payment_method: {
|
|
8360
|
+
type: [
|
|
8361
|
+
"string",
|
|
8362
|
+
"null"
|
|
8363
|
+
]
|
|
8364
|
+
},
|
|
8094
8365
|
payer_wallet: {
|
|
8095
8366
|
type: [
|
|
8096
8367
|
"string",
|
|
@@ -8118,11 +8389,19 @@ var docs_data_default = {
|
|
|
8118
8389
|
"null"
|
|
8119
8390
|
]
|
|
8120
8391
|
},
|
|
8392
|
+
received_token: {
|
|
8393
|
+
type: [
|
|
8394
|
+
"string",
|
|
8395
|
+
"null"
|
|
8396
|
+
],
|
|
8397
|
+
description: "Token contract/mint actually received at the deposit address."
|
|
8398
|
+
},
|
|
8121
8399
|
fee_amount: {
|
|
8122
8400
|
type: [
|
|
8123
8401
|
"string",
|
|
8124
8402
|
"null"
|
|
8125
|
-
]
|
|
8403
|
+
],
|
|
8404
|
+
description: "Platform fee in 6-decimal units (same scale as amount_total)."
|
|
8126
8405
|
},
|
|
8127
8406
|
fee_rate: {
|
|
8128
8407
|
type: [
|
|
@@ -8134,7 +8413,22 @@ var docs_data_default = {
|
|
|
8134
8413
|
type: [
|
|
8135
8414
|
"string",
|
|
8136
8415
|
"null"
|
|
8137
|
-
]
|
|
8416
|
+
],
|
|
8417
|
+
description: "Merchant net in 6-decimal units (same scale as amount_total)."
|
|
8418
|
+
},
|
|
8419
|
+
fee_amount_token: {
|
|
8420
|
+
type: [
|
|
8421
|
+
"string",
|
|
8422
|
+
"null"
|
|
8423
|
+
],
|
|
8424
|
+
description: "Platform fee in the payment token atomic units (token_decimals) \u2014 exactly what is transferred on-chain. Null before a deposit address is issued."
|
|
8425
|
+
},
|
|
8426
|
+
net_amount_token: {
|
|
8427
|
+
type: [
|
|
8428
|
+
"string",
|
|
8429
|
+
"null"
|
|
8430
|
+
],
|
|
8431
|
+
description: "Merchant net in the payment token atomic units (token_decimals) \u2014 exactly what is swept on-chain. Null before a deposit address is issued."
|
|
8138
8432
|
},
|
|
8139
8433
|
tx_hash: {
|
|
8140
8434
|
type: [
|
|
@@ -8142,6 +8436,12 @@ var docs_data_default = {
|
|
|
8142
8436
|
"null"
|
|
8143
8437
|
]
|
|
8144
8438
|
},
|
|
8439
|
+
deposit_tx_hash: {
|
|
8440
|
+
type: [
|
|
8441
|
+
"string",
|
|
8442
|
+
"null"
|
|
8443
|
+
]
|
|
8444
|
+
},
|
|
8145
8445
|
sweep_tx_hash: {
|
|
8146
8446
|
type: [
|
|
8147
8447
|
"string",
|
|
@@ -8168,7 +8468,10 @@ var docs_data_default = {
|
|
|
8168
8468
|
additionalProperties: true
|
|
8169
8469
|
},
|
|
8170
8470
|
payment_source: {
|
|
8171
|
-
type:
|
|
8471
|
+
type: [
|
|
8472
|
+
"string",
|
|
8473
|
+
"null"
|
|
8474
|
+
]
|
|
8172
8475
|
},
|
|
8173
8476
|
payment_link_id: {
|
|
8174
8477
|
type: [
|
|
@@ -8213,9 +8516,6 @@ var docs_data_default = {
|
|
|
8213
8516
|
id: {
|
|
8214
8517
|
type: "string"
|
|
8215
8518
|
},
|
|
8216
|
-
session_id: {
|
|
8217
|
-
type: "string"
|
|
8218
|
-
},
|
|
8219
8519
|
name: {
|
|
8220
8520
|
type: "string"
|
|
8221
8521
|
},
|
|
@@ -8253,7 +8553,6 @@ var docs_data_default = {
|
|
|
8253
8553
|
},
|
|
8254
8554
|
required: [
|
|
8255
8555
|
"id",
|
|
8256
|
-
"session_id",
|
|
8257
8556
|
"name",
|
|
8258
8557
|
"amount",
|
|
8259
8558
|
"currency",
|
|
@@ -8262,7 +8561,7 @@ var docs_data_default = {
|
|
|
8262
8561
|
"product_id",
|
|
8263
8562
|
"created_at"
|
|
8264
8563
|
],
|
|
8265
|
-
additionalProperties:
|
|
8564
|
+
additionalProperties: false
|
|
8266
8565
|
}
|
|
8267
8566
|
}
|
|
8268
8567
|
},
|
|
@@ -8275,15 +8574,21 @@ var docs_data_default = {
|
|
|
8275
8574
|
"currency",
|
|
8276
8575
|
"pay_chain",
|
|
8277
8576
|
"pay_asset",
|
|
8577
|
+
"token_decimals",
|
|
8578
|
+
"payment_method",
|
|
8278
8579
|
"payer_wallet",
|
|
8279
8580
|
"seller_wallet",
|
|
8280
8581
|
"tolerance_percent",
|
|
8281
8582
|
"deposit_address",
|
|
8282
8583
|
"refund_address",
|
|
8584
|
+
"received_token",
|
|
8283
8585
|
"fee_amount",
|
|
8284
8586
|
"fee_rate",
|
|
8285
8587
|
"net_amount",
|
|
8588
|
+
"fee_amount_token",
|
|
8589
|
+
"net_amount_token",
|
|
8286
8590
|
"tx_hash",
|
|
8591
|
+
"deposit_tx_hash",
|
|
8287
8592
|
"sweep_tx_hash",
|
|
8288
8593
|
"success_url",
|
|
8289
8594
|
"cancel_url",
|
|
@@ -8296,7 +8601,7 @@ var docs_data_default = {
|
|
|
8296
8601
|
"created_at",
|
|
8297
8602
|
"line_items"
|
|
8298
8603
|
],
|
|
8299
|
-
additionalProperties:
|
|
8604
|
+
additionalProperties: false
|
|
8300
8605
|
},
|
|
8301
8606
|
CheckoutSessionCreatedEventData: {
|
|
8302
8607
|
type: "object",
|
|
@@ -8384,7 +8689,21 @@ var docs_data_default = {
|
|
|
8384
8689
|
},
|
|
8385
8690
|
reason: {
|
|
8386
8691
|
type: "string",
|
|
8387
|
-
const: "no_deposit"
|
|
8692
|
+
const: "no_deposit",
|
|
8693
|
+
description: "Set when nothing was paid."
|
|
8694
|
+
},
|
|
8695
|
+
expired_at: {
|
|
8696
|
+
type: "string",
|
|
8697
|
+
format: "date-time",
|
|
8698
|
+
description: "When the session was marked expired. Sent for sessions that expired before a deposit address was issued."
|
|
8699
|
+
},
|
|
8700
|
+
source: {
|
|
8701
|
+
type: "string",
|
|
8702
|
+
enum: [
|
|
8703
|
+
"lazy",
|
|
8704
|
+
"cron"
|
|
8705
|
+
],
|
|
8706
|
+
description: "What noticed the expiry: a read of the session (`lazy`) or the scheduled sweep (`cron`). Informational."
|
|
8388
8707
|
},
|
|
8389
8708
|
refund_tx_hash: {
|
|
8390
8709
|
type: "string",
|
|
@@ -8441,6 +8760,19 @@ var docs_data_default = {
|
|
|
8441
8760
|
"null"
|
|
8442
8761
|
]
|
|
8443
8762
|
},
|
|
8763
|
+
token_decimals: {
|
|
8764
|
+
type: [
|
|
8765
|
+
"integer",
|
|
8766
|
+
"null"
|
|
8767
|
+
],
|
|
8768
|
+
description: "Decimals of the payment token (USDC/USDT = 6, BSC-USD = 18). `amount_received` is in these units; `amount_total` is always 6-decimal."
|
|
8769
|
+
},
|
|
8770
|
+
payment_method: {
|
|
8771
|
+
type: [
|
|
8772
|
+
"string",
|
|
8773
|
+
"null"
|
|
8774
|
+
]
|
|
8775
|
+
},
|
|
8444
8776
|
payer_wallet: {
|
|
8445
8777
|
type: [
|
|
8446
8778
|
"string",
|
|
@@ -8468,11 +8800,19 @@ var docs_data_default = {
|
|
|
8468
8800
|
"null"
|
|
8469
8801
|
]
|
|
8470
8802
|
},
|
|
8803
|
+
received_token: {
|
|
8804
|
+
type: [
|
|
8805
|
+
"string",
|
|
8806
|
+
"null"
|
|
8807
|
+
],
|
|
8808
|
+
description: "Token contract/mint actually received at the deposit address."
|
|
8809
|
+
},
|
|
8471
8810
|
fee_amount: {
|
|
8472
8811
|
type: [
|
|
8473
8812
|
"string",
|
|
8474
8813
|
"null"
|
|
8475
|
-
]
|
|
8814
|
+
],
|
|
8815
|
+
description: "Platform fee in 6-decimal units (same scale as amount_total)."
|
|
8476
8816
|
},
|
|
8477
8817
|
fee_rate: {
|
|
8478
8818
|
type: [
|
|
@@ -8484,7 +8824,22 @@ var docs_data_default = {
|
|
|
8484
8824
|
type: [
|
|
8485
8825
|
"string",
|
|
8486
8826
|
"null"
|
|
8487
|
-
]
|
|
8827
|
+
],
|
|
8828
|
+
description: "Merchant net in 6-decimal units (same scale as amount_total)."
|
|
8829
|
+
},
|
|
8830
|
+
fee_amount_token: {
|
|
8831
|
+
type: [
|
|
8832
|
+
"string",
|
|
8833
|
+
"null"
|
|
8834
|
+
],
|
|
8835
|
+
description: "Platform fee in the payment token atomic units (token_decimals) \u2014 exactly what is transferred on-chain. Null before a deposit address is issued."
|
|
8836
|
+
},
|
|
8837
|
+
net_amount_token: {
|
|
8838
|
+
type: [
|
|
8839
|
+
"string",
|
|
8840
|
+
"null"
|
|
8841
|
+
],
|
|
8842
|
+
description: "Merchant net in the payment token atomic units (token_decimals) \u2014 exactly what is swept on-chain. Null before a deposit address is issued."
|
|
8488
8843
|
},
|
|
8489
8844
|
tx_hash: {
|
|
8490
8845
|
type: [
|
|
@@ -8492,6 +8847,12 @@ var docs_data_default = {
|
|
|
8492
8847
|
"null"
|
|
8493
8848
|
]
|
|
8494
8849
|
},
|
|
8850
|
+
deposit_tx_hash: {
|
|
8851
|
+
type: [
|
|
8852
|
+
"string",
|
|
8853
|
+
"null"
|
|
8854
|
+
]
|
|
8855
|
+
},
|
|
8495
8856
|
sweep_tx_hash: {
|
|
8496
8857
|
type: [
|
|
8497
8858
|
"string",
|
|
@@ -8518,7 +8879,10 @@ var docs_data_default = {
|
|
|
8518
8879
|
additionalProperties: true
|
|
8519
8880
|
},
|
|
8520
8881
|
payment_source: {
|
|
8521
|
-
type:
|
|
8882
|
+
type: [
|
|
8883
|
+
"string",
|
|
8884
|
+
"null"
|
|
8885
|
+
]
|
|
8522
8886
|
},
|
|
8523
8887
|
payment_link_id: {
|
|
8524
8888
|
type: [
|
|
@@ -8556,7 +8920,8 @@ var docs_data_default = {
|
|
|
8556
8920
|
description: "ISO-8601 timestamp"
|
|
8557
8921
|
},
|
|
8558
8922
|
raw_status: {
|
|
8559
|
-
type: "string"
|
|
8923
|
+
type: "string",
|
|
8924
|
+
description: "Same as `status`; kept for compatibility."
|
|
8560
8925
|
}
|
|
8561
8926
|
},
|
|
8562
8927
|
required: [
|
|
@@ -8568,15 +8933,21 @@ var docs_data_default = {
|
|
|
8568
8933
|
"currency",
|
|
8569
8934
|
"pay_chain",
|
|
8570
8935
|
"pay_asset",
|
|
8936
|
+
"token_decimals",
|
|
8937
|
+
"payment_method",
|
|
8571
8938
|
"payer_wallet",
|
|
8572
8939
|
"seller_wallet",
|
|
8573
8940
|
"tolerance_percent",
|
|
8574
8941
|
"deposit_address",
|
|
8575
8942
|
"refund_address",
|
|
8943
|
+
"received_token",
|
|
8576
8944
|
"fee_amount",
|
|
8577
8945
|
"fee_rate",
|
|
8578
8946
|
"net_amount",
|
|
8947
|
+
"fee_amount_token",
|
|
8948
|
+
"net_amount_token",
|
|
8579
8949
|
"tx_hash",
|
|
8950
|
+
"deposit_tx_hash",
|
|
8580
8951
|
"sweep_tx_hash",
|
|
8581
8952
|
"success_url",
|
|
8582
8953
|
"cancel_url",
|
|
@@ -8589,7 +8960,7 @@ var docs_data_default = {
|
|
|
8589
8960
|
"created_at",
|
|
8590
8961
|
"raw_status"
|
|
8591
8962
|
],
|
|
8592
|
-
additionalProperties:
|
|
8963
|
+
additionalProperties: false
|
|
8593
8964
|
},
|
|
8594
8965
|
CheckoutSessionPaidEventData: {
|
|
8595
8966
|
type: "object",
|
|
@@ -8776,7 +9147,24 @@ var docs_data_default = {
|
|
|
8776
9147
|
"name",
|
|
8777
9148
|
"currency"
|
|
8778
9149
|
],
|
|
8779
|
-
additionalProperties: true
|
|
9150
|
+
additionalProperties: true,
|
|
9151
|
+
oneOf: [
|
|
9152
|
+
{
|
|
9153
|
+
required: [
|
|
9154
|
+
"amount"
|
|
9155
|
+
]
|
|
9156
|
+
},
|
|
9157
|
+
{
|
|
9158
|
+
required: [
|
|
9159
|
+
"amount_atomic"
|
|
9160
|
+
]
|
|
9161
|
+
}
|
|
9162
|
+
],
|
|
9163
|
+
not: {
|
|
9164
|
+
required: [
|
|
9165
|
+
"price_id"
|
|
9166
|
+
]
|
|
9167
|
+
}
|
|
8780
9168
|
}
|
|
8781
9169
|
]
|
|
8782
9170
|
},
|
|
@@ -8790,12 +9178,16 @@ var docs_data_default = {
|
|
|
8790
9178
|
success_url: {
|
|
8791
9179
|
description: "Where the buyer returns after paying. `{CHECKOUT_SESSION_ID}` is replaced with the session id.",
|
|
8792
9180
|
type: "string",
|
|
8793
|
-
format: "uri"
|
|
9181
|
+
format: "uri",
|
|
9182
|
+
maxLength: 2048,
|
|
9183
|
+
pattern: "^https?://"
|
|
8794
9184
|
},
|
|
8795
9185
|
cancel_url: {
|
|
8796
9186
|
description: "Where the buyer goes when cancelling.",
|
|
8797
9187
|
type: "string",
|
|
8798
|
-
format: "uri"
|
|
9188
|
+
format: "uri",
|
|
9189
|
+
maxLength: 2048,
|
|
9190
|
+
pattern: "^https?://"
|
|
8799
9191
|
},
|
|
8800
9192
|
customer_email: {
|
|
8801
9193
|
description: "Buyer email for the receipt.",
|
|
@@ -8815,15 +9207,16 @@ var docs_data_default = {
|
|
|
8815
9207
|
default: 15
|
|
8816
9208
|
},
|
|
8817
9209
|
tolerance_percent: {
|
|
8818
|
-
description:
|
|
8819
|
-
type: "string"
|
|
9210
|
+
description: 'Deprecated. Underpayments are never accepted \u2014 a short payment becomes `unresolved_underpaid` and can be topped up to the same address. Only `"0"` is accepted; any other value returns 400.',
|
|
9211
|
+
type: "string",
|
|
9212
|
+
pattern: "^0+(\\.0+)?$"
|
|
8820
9213
|
},
|
|
8821
9214
|
pay_chain: {
|
|
8822
|
-
description: "Fix the payment network
|
|
9215
|
+
description: "Fix the payment network: CAIP-2 (`eip155:84532`) or a short name (`base`), which resolves to the testnet in test mode and the mainnet in live mode. Mutually exclusive with `allowed_pay_chains`.",
|
|
8823
9216
|
type: "string"
|
|
8824
9217
|
},
|
|
8825
9218
|
allowed_pay_chains: {
|
|
8826
|
-
description: "Networks the buyer may choose from (CAIP-2).",
|
|
9219
|
+
description: "Networks the buyer may choose from (CAIP-2 or short names, resolved like `pay_chain`).",
|
|
8827
9220
|
type: "array",
|
|
8828
9221
|
items: {
|
|
8829
9222
|
type: "string",
|
|
@@ -8885,7 +9278,7 @@ var docs_data_default = {
|
|
|
8885
9278
|
default: true
|
|
8886
9279
|
},
|
|
8887
9280
|
email_required: {
|
|
8888
|
-
description: "Require the email. Default true; needs `collect_email`.",
|
|
9281
|
+
description: "Require the email: the hosted link page asks for it and no checkout is created without it. Default true; needs `collect_email`.",
|
|
8889
9282
|
type: "boolean",
|
|
8890
9283
|
default: true
|
|
8891
9284
|
},
|
|
@@ -8925,11 +9318,15 @@ var docs_data_default = {
|
|
|
8925
9318
|
},
|
|
8926
9319
|
success_url: {
|
|
8927
9320
|
type: "string",
|
|
8928
|
-
format: "uri"
|
|
9321
|
+
format: "uri",
|
|
9322
|
+
maxLength: 2048,
|
|
9323
|
+
pattern: "^https?://"
|
|
8929
9324
|
},
|
|
8930
9325
|
cancel_url: {
|
|
8931
9326
|
type: "string",
|
|
8932
|
-
format: "uri"
|
|
9327
|
+
format: "uri",
|
|
9328
|
+
maxLength: 2048,
|
|
9329
|
+
pattern: "^https?://"
|
|
8933
9330
|
},
|
|
8934
9331
|
max_uses: {
|
|
8935
9332
|
description: "Stop accepting payments after this many paid uses.",
|
|
@@ -9008,7 +9405,9 @@ var docs_data_default = {
|
|
|
9008
9405
|
},
|
|
9009
9406
|
image_url: {
|
|
9010
9407
|
type: "string",
|
|
9011
|
-
format: "uri"
|
|
9408
|
+
format: "uri",
|
|
9409
|
+
maxLength: 2048,
|
|
9410
|
+
pattern: "^https?://"
|
|
9012
9411
|
},
|
|
9013
9412
|
metadata: {
|
|
9014
9413
|
type: "object",
|
|
@@ -9037,12 +9436,12 @@ var docs_data_default = {
|
|
|
9037
9436
|
minItems: 1
|
|
9038
9437
|
},
|
|
9039
9438
|
retry_policy: {
|
|
9040
|
-
description: "
|
|
9439
|
+
description: "`max_retries` caps the total delivery attempts (1\u201320, default 10: 3 right away, then 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h). The schedule itself is fixed; `backoff` is ignored.",
|
|
9041
9440
|
type: "object",
|
|
9042
9441
|
properties: {
|
|
9043
9442
|
max_retries: {
|
|
9044
9443
|
type: "integer",
|
|
9045
|
-
minimum:
|
|
9444
|
+
minimum: 1,
|
|
9046
9445
|
maximum: 20
|
|
9047
9446
|
},
|
|
9048
9447
|
backoff: {
|
|
@@ -9173,6 +9572,13 @@ var docs_data_default = {
|
|
|
9173
9572
|
],
|
|
9174
9573
|
format: "date-time",
|
|
9175
9574
|
description: "ISO-8601 timestamp"
|
|
9575
|
+
},
|
|
9576
|
+
depositOwner: {
|
|
9577
|
+
type: [
|
|
9578
|
+
"string",
|
|
9579
|
+
"null"
|
|
9580
|
+
],
|
|
9581
|
+
description: "Solana only: the wallet (owner) address to send to \u2014 the order account that owns the deposit token account. Wallets derive the token account from this address, so use it as the recipient. Null on EVM/Tron."
|
|
9176
9582
|
}
|
|
9177
9583
|
},
|
|
9178
9584
|
required: [
|
|
@@ -9184,7 +9590,8 @@ var docs_data_default = {
|
|
|
9184
9590
|
"feeAmount",
|
|
9185
9591
|
"refundAddress",
|
|
9186
9592
|
"deadline",
|
|
9187
|
-
"expiresAt"
|
|
9593
|
+
"expiresAt",
|
|
9594
|
+
"depositOwner"
|
|
9188
9595
|
]
|
|
9189
9596
|
},
|
|
9190
9597
|
ErrorResponse: {
|
|
@@ -9192,18 +9599,15 @@ var docs_data_default = {
|
|
|
9192
9599
|
properties: {
|
|
9193
9600
|
error: {
|
|
9194
9601
|
type: "string",
|
|
9195
|
-
description: "Human-readable message
|
|
9602
|
+
description: "Human-readable message."
|
|
9196
9603
|
},
|
|
9197
9604
|
code: {
|
|
9198
9605
|
type: "string",
|
|
9199
|
-
description: "Machine-readable error code, e.g. VALIDATION_FAILED."
|
|
9200
|
-
},
|
|
9201
|
-
message: {
|
|
9202
|
-
type: "string",
|
|
9203
|
-
description: "Description, when `error` carries the code."
|
|
9606
|
+
description: "Machine-readable error code, e.g. VALIDATION_FAILED. Branch on this, not on `error`."
|
|
9204
9607
|
},
|
|
9205
9608
|
retryable: {
|
|
9206
|
-
type: "boolean"
|
|
9609
|
+
type: "boolean",
|
|
9610
|
+
description: "Whether retrying the same request may succeed."
|
|
9207
9611
|
},
|
|
9208
9612
|
trace_id: {
|
|
9209
9613
|
type: "string",
|
|
@@ -9214,7 +9618,9 @@ var docs_data_default = {
|
|
|
9214
9618
|
}
|
|
9215
9619
|
},
|
|
9216
9620
|
required: [
|
|
9217
|
-
"error"
|
|
9621
|
+
"error",
|
|
9622
|
+
"code",
|
|
9623
|
+
"retryable"
|
|
9218
9624
|
],
|
|
9219
9625
|
additionalProperties: true
|
|
9220
9626
|
},
|
|
@@ -9611,6 +10017,24 @@ var docs_data_default = {
|
|
|
9611
10017
|
amount_atomic: {
|
|
9612
10018
|
type: "string",
|
|
9613
10019
|
description: "Exact amount to pay in this token, in its own decimals."
|
|
10020
|
+
},
|
|
10021
|
+
available: {
|
|
10022
|
+
type: "boolean",
|
|
10023
|
+
description: "false = shown but not selectable yet (see unavailable_reason). Deposit-address requests for it are rejected with CHECKOUT_CHAIN_COMING_SOON."
|
|
10024
|
+
},
|
|
10025
|
+
unavailable_reason: {
|
|
10026
|
+
anyOf: [
|
|
10027
|
+
{
|
|
10028
|
+
type: "string",
|
|
10029
|
+
enum: [
|
|
10030
|
+
"coming_soon"
|
|
10031
|
+
]
|
|
10032
|
+
},
|
|
10033
|
+
{
|
|
10034
|
+
type: "null"
|
|
10035
|
+
}
|
|
10036
|
+
],
|
|
10037
|
+
description: "Why the option is not selectable. coming_soon = this network opens for payments soon."
|
|
9614
10038
|
}
|
|
9615
10039
|
},
|
|
9616
10040
|
required: [
|
|
@@ -9619,7 +10043,9 @@ var docs_data_default = {
|
|
|
9619
10043
|
"token_symbol",
|
|
9620
10044
|
"token_address",
|
|
9621
10045
|
"decimals",
|
|
9622
|
-
"amount_atomic"
|
|
10046
|
+
"amount_atomic",
|
|
10047
|
+
"available",
|
|
10048
|
+
"unavailable_reason"
|
|
9623
10049
|
]
|
|
9624
10050
|
}
|
|
9625
10051
|
},
|
|
@@ -9675,13 +10101,6 @@ var docs_data_default = {
|
|
|
9675
10101
|
"null"
|
|
9676
10102
|
]
|
|
9677
10103
|
},
|
|
9678
|
-
metadata: {
|
|
9679
|
-
type: [
|
|
9680
|
-
"object",
|
|
9681
|
-
"null"
|
|
9682
|
-
],
|
|
9683
|
-
additionalProperties: true
|
|
9684
|
-
},
|
|
9685
10104
|
fee_amount: {
|
|
9686
10105
|
type: [
|
|
9687
10106
|
"string",
|
|
@@ -9724,6 +10143,21 @@ var docs_data_default = {
|
|
|
9724
10143
|
"null"
|
|
9725
10144
|
]
|
|
9726
10145
|
},
|
|
10146
|
+
deposit_owner: {
|
|
10147
|
+
type: [
|
|
10148
|
+
"string",
|
|
10149
|
+
"null"
|
|
10150
|
+
],
|
|
10151
|
+
description: "Solana only: the wallet (owner) address to send to \u2014 the order account that owns the deposit token account. Wallets derive the token account from this address, so use it as the recipient. Null on EVM/Tron."
|
|
10152
|
+
},
|
|
10153
|
+
payment_deadline_at: {
|
|
10154
|
+
type: [
|
|
10155
|
+
"string",
|
|
10156
|
+
"null"
|
|
10157
|
+
],
|
|
10158
|
+
format: "date-time",
|
|
10159
|
+
description: "ISO-8601 timestamp"
|
|
10160
|
+
},
|
|
9727
10161
|
payment_method: {
|
|
9728
10162
|
type: [
|
|
9729
10163
|
"string",
|
|
@@ -9854,7 +10288,6 @@ var docs_data_default = {
|
|
|
9854
10288
|
"expires_at",
|
|
9855
10289
|
"success_url",
|
|
9856
10290
|
"cancel_url",
|
|
9857
|
-
"metadata",
|
|
9858
10291
|
"fee_amount",
|
|
9859
10292
|
"fee_rate",
|
|
9860
10293
|
"net_amount",
|
|
@@ -9863,6 +10296,8 @@ var docs_data_default = {
|
|
|
9863
10296
|
"pay_chain",
|
|
9864
10297
|
"pay_asset",
|
|
9865
10298
|
"deposit_address",
|
|
10299
|
+
"deposit_owner",
|
|
10300
|
+
"payment_deadline_at",
|
|
9866
10301
|
"payment_method",
|
|
9867
10302
|
"payer_wallet",
|
|
9868
10303
|
"seller_wallet",
|
|
@@ -9907,6 +10342,31 @@ var docs_data_default = {
|
|
|
9907
10342
|
],
|
|
9908
10343
|
description: "Added to responses of authenticated endpoints. List endpoints may also carry total/limit/offset."
|
|
9909
10344
|
},
|
|
10345
|
+
RotateWebhookSecretRequest: {
|
|
10346
|
+
type: "object",
|
|
10347
|
+
properties: {
|
|
10348
|
+
reason_code: {
|
|
10349
|
+
description: "One of user_request, security_concern, rotation, no_longer_needed, other.",
|
|
10350
|
+
type: "string",
|
|
10351
|
+
enum: [
|
|
10352
|
+
"user_request",
|
|
10353
|
+
"security_concern",
|
|
10354
|
+
"rotation",
|
|
10355
|
+
"no_longer_needed",
|
|
10356
|
+
"other"
|
|
10357
|
+
]
|
|
10358
|
+
},
|
|
10359
|
+
reason_note: {
|
|
10360
|
+
description: "Optional free text (\u2264 500 chars) stored with the audit record.",
|
|
10361
|
+
type: "string",
|
|
10362
|
+
maxLength: 500
|
|
10363
|
+
}
|
|
10364
|
+
},
|
|
10365
|
+
required: [
|
|
10366
|
+
"reason_code"
|
|
10367
|
+
],
|
|
10368
|
+
"x-unknown-keys": "strip"
|
|
10369
|
+
},
|
|
9910
10370
|
SendTestWebhookRequest: {
|
|
9911
10371
|
type: "object",
|
|
9912
10372
|
properties: {
|
|
@@ -10065,14 +10525,18 @@ var docs_data_default = {
|
|
|
10065
10525
|
"string",
|
|
10066
10526
|
"null"
|
|
10067
10527
|
],
|
|
10068
|
-
format: "uri"
|
|
10528
|
+
format: "uri",
|
|
10529
|
+
maxLength: 2048,
|
|
10530
|
+
pattern: "^https?://"
|
|
10069
10531
|
},
|
|
10070
10532
|
cancel_url: {
|
|
10071
10533
|
type: [
|
|
10072
10534
|
"string",
|
|
10073
10535
|
"null"
|
|
10074
10536
|
],
|
|
10075
|
-
format: "uri"
|
|
10537
|
+
format: "uri",
|
|
10538
|
+
maxLength: 2048,
|
|
10539
|
+
pattern: "^https?://"
|
|
10076
10540
|
},
|
|
10077
10541
|
status: {
|
|
10078
10542
|
type: "string",
|
|
@@ -10135,7 +10599,9 @@ var docs_data_default = {
|
|
|
10135
10599
|
"string",
|
|
10136
10600
|
"null"
|
|
10137
10601
|
],
|
|
10138
|
-
format: "uri"
|
|
10602
|
+
format: "uri",
|
|
10603
|
+
maxLength: 2048,
|
|
10604
|
+
pattern: "^https?://"
|
|
10139
10605
|
},
|
|
10140
10606
|
metadata: {
|
|
10141
10607
|
type: "object",
|
|
@@ -10177,7 +10643,7 @@ var docs_data_default = {
|
|
|
10177
10643
|
properties: {
|
|
10178
10644
|
max_retries: {
|
|
10179
10645
|
type: "integer",
|
|
10180
|
-
minimum:
|
|
10646
|
+
minimum: 1,
|
|
10181
10647
|
maximum: 20
|
|
10182
10648
|
},
|
|
10183
10649
|
backoff: {
|
|
@@ -10197,6 +10663,22 @@ var docs_data_default = {
|
|
|
10197
10663
|
"null"
|
|
10198
10664
|
],
|
|
10199
10665
|
format: "uri"
|
|
10666
|
+
},
|
|
10667
|
+
reason_code: {
|
|
10668
|
+
description: "Required when `status` is `disabled`: one of user_request, security_concern, rotation, no_longer_needed, other.",
|
|
10669
|
+
type: "string",
|
|
10670
|
+
enum: [
|
|
10671
|
+
"user_request",
|
|
10672
|
+
"security_concern",
|
|
10673
|
+
"rotation",
|
|
10674
|
+
"no_longer_needed",
|
|
10675
|
+
"other"
|
|
10676
|
+
]
|
|
10677
|
+
},
|
|
10678
|
+
reason_note: {
|
|
10679
|
+
description: "Optional free text (\u2264 500 chars) stored with the audit record.",
|
|
10680
|
+
type: "string",
|
|
10681
|
+
maxLength: 500
|
|
10200
10682
|
}
|
|
10201
10683
|
},
|
|
10202
10684
|
"x-unknown-keys": "strip"
|
|
@@ -10285,22 +10767,23 @@ var docs_data_default = {
|
|
|
10285
10767
|
type: "string",
|
|
10286
10768
|
format: "uuid"
|
|
10287
10769
|
},
|
|
10288
|
-
|
|
10289
|
-
type: "string"
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10770
|
+
mode: {
|
|
10771
|
+
type: "string",
|
|
10772
|
+
enum: [
|
|
10773
|
+
"TEST",
|
|
10774
|
+
"LIVE"
|
|
10775
|
+
]
|
|
10293
10776
|
},
|
|
10294
|
-
|
|
10777
|
+
endpoint_url: {
|
|
10295
10778
|
type: "string"
|
|
10296
10779
|
},
|
|
10297
|
-
|
|
10780
|
+
subscribed_events: {
|
|
10298
10781
|
type: "array",
|
|
10299
10782
|
items: {
|
|
10300
10783
|
type: "string"
|
|
10301
10784
|
}
|
|
10302
10785
|
},
|
|
10303
|
-
|
|
10786
|
+
retry_policy: {
|
|
10304
10787
|
type: "object",
|
|
10305
10788
|
properties: {
|
|
10306
10789
|
max_retries: {
|
|
@@ -10316,7 +10799,7 @@ var docs_data_default = {
|
|
|
10316
10799
|
}
|
|
10317
10800
|
}
|
|
10318
10801
|
},
|
|
10319
|
-
|
|
10802
|
+
dead_letter_destination: {
|
|
10320
10803
|
type: [
|
|
10321
10804
|
"string",
|
|
10322
10805
|
"null"
|
|
@@ -10330,12 +10813,12 @@ var docs_data_default = {
|
|
|
10330
10813
|
"disabled"
|
|
10331
10814
|
]
|
|
10332
10815
|
},
|
|
10333
|
-
|
|
10816
|
+
created_at: {
|
|
10334
10817
|
type: "string",
|
|
10335
10818
|
format: "date-time",
|
|
10336
10819
|
description: "ISO-8601 timestamp"
|
|
10337
10820
|
},
|
|
10338
|
-
|
|
10821
|
+
updated_at: {
|
|
10339
10822
|
type: "string",
|
|
10340
10823
|
format: "date-time",
|
|
10341
10824
|
description: "ISO-8601 timestamp"
|
|
@@ -10343,15 +10826,14 @@ var docs_data_default = {
|
|
|
10343
10826
|
},
|
|
10344
10827
|
required: [
|
|
10345
10828
|
"id",
|
|
10346
|
-
"
|
|
10347
|
-
"
|
|
10348
|
-
"
|
|
10349
|
-
"
|
|
10350
|
-
"
|
|
10351
|
-
"deadLetterDestination",
|
|
10829
|
+
"mode",
|
|
10830
|
+
"endpoint_url",
|
|
10831
|
+
"subscribed_events",
|
|
10832
|
+
"retry_policy",
|
|
10833
|
+
"dead_letter_destination",
|
|
10352
10834
|
"status",
|
|
10353
|
-
"
|
|
10354
|
-
"
|
|
10835
|
+
"created_at",
|
|
10836
|
+
"updated_at"
|
|
10355
10837
|
]
|
|
10356
10838
|
},
|
|
10357
10839
|
WebhookEndpointWithSecret: {
|
|
@@ -10934,7 +11416,7 @@ var docs_data_default = {
|
|
|
10934
11416
|
default: "USDC"
|
|
10935
11417
|
},
|
|
10936
11418
|
payTo: {
|
|
10937
|
-
description: "Your payout wallet (0x)
|
|
11419
|
+
description: "Your payout wallet (0x) \u2014 must be the one registered in Console for this mode, otherwise `PAYTO_NOT_MERCHANT_WALLET`.",
|
|
10938
11420
|
type: "string",
|
|
10939
11421
|
pattern: "^0x[a-fA-F0-9]{40}$"
|
|
10940
11422
|
},
|
|
@@ -11246,6 +11728,7 @@ var FLOWS = {
|
|
|
11246
11728
|
};
|
|
11247
11729
|
var SCENARIOS = ["exact", "underpay", "overpay", "wrong_token"];
|
|
11248
11730
|
var json = (v) => JSON.stringify(v, null, 2);
|
|
11731
|
+
var FULL_SECTION_CHARS = 2500;
|
|
11249
11732
|
var docTools = [
|
|
11250
11733
|
tool({
|
|
11251
11734
|
name: "search_docs",
|
|
@@ -11266,7 +11749,7 @@ var docTools = [
|
|
|
11266
11749
|
return hits.map((h, i) => `## ${i + 1}. ${h.section.pageTitle} \u2192 ${h.section.heading}
|
|
11267
11750
|
page: ${h.section.page} \xB7 ${h.section.url}
|
|
11268
11751
|
|
|
11269
|
-
${snippet(h.section.text, query)}`).join("\n\n---\n\n");
|
|
11752
|
+
${h.section.text.length <= FULL_SECTION_CHARS ? h.section.text : snippet(h.section.text, query)}`).join("\n\n---\n\n");
|
|
11270
11753
|
}
|
|
11271
11754
|
}),
|
|
11272
11755
|
tool({
|
|
@@ -11351,7 +11834,7 @@ ${e.description}` : `"${code}" is not in the documented list. Branch on the HTTP
|
|
|
11351
11834
|
var actionTools = [
|
|
11352
11835
|
tool({
|
|
11353
11836
|
name: "create_test_session",
|
|
11354
|
-
description: "Create a BitPal TEST-mode checkout session (testnet)
|
|
11837
|
+
description: "Create a BitPal TEST-mode checkout session (testnet) for one chain and token, ready for simulate_payment. Requires a bp_test_ API key.",
|
|
11355
11838
|
inputSchema: {
|
|
11356
11839
|
type: "object",
|
|
11357
11840
|
properties: {
|
|
@@ -11378,24 +11861,13 @@ var actionTools = [
|
|
|
11378
11861
|
allowed_assets: [{ caip2: a.chain, token: a.token }],
|
|
11379
11862
|
expires_in_minutes: a.expires_in_minutes
|
|
11380
11863
|
});
|
|
11381
|
-
const pub = await api.request("GET", `/v1/checkout/sessions/${s.id}/public`, void 0, { auth: false });
|
|
11382
|
-
const opt = pub.payment_options[0];
|
|
11383
|
-
if (!opt || !pub.seller_wallet) return json({ ...s, session_token: void 0, note: "Session created, but no deposit address: add a payout wallet in the console first." });
|
|
11384
|
-
const dep = await api.request("POST", `/v1/checkout/sessions/${s.id}/deposit-address`, {
|
|
11385
|
-
session_token: s.session_token,
|
|
11386
|
-
refundAddress: pub.seller_wallet,
|
|
11387
|
-
chain: opt.caip2,
|
|
11388
|
-
token: opt.token_symbol
|
|
11389
|
-
}, { auth: false });
|
|
11390
11864
|
return json({
|
|
11391
11865
|
session_id: s.id,
|
|
11392
11866
|
checkout_url: s.checkout_url,
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
deposit_address: dep.depositAddress,
|
|
11396
|
-
expected_amount: dep.expectedAmount,
|
|
11867
|
+
status: s.status,
|
|
11868
|
+
amount_total: s.amount_total,
|
|
11397
11869
|
expires_at: s.expires_at,
|
|
11398
|
-
next: "Call simulate_payment with this session_id, then list_test_events to see the webhooks."
|
|
11870
|
+
next: "Call simulate_payment with this session_id (it issues the deposit address on first use), then list_test_events to see the webhooks."
|
|
11399
11871
|
});
|
|
11400
11872
|
}
|
|
11401
11873
|
}),
|
|
@@ -11417,7 +11889,7 @@ var actionTools = [
|
|
|
11417
11889
|
inputSchema: { type: "object", properties: { session_id: { type: "string" } }, required: ["session_id"] },
|
|
11418
11890
|
schema: external_exports.object({ session_id: external_exports.string().regex(/^cs_[A-Za-z0-9_-]+$/) }),
|
|
11419
11891
|
requiresTestKey: true,
|
|
11420
|
-
run: async ({ session_id }, ctx2) => json(await new BitPalClient(ctx2.apiKey, ctx2.apiBase).request("GET", `/v1/checkout/sessions/${session_id}/status
|
|
11892
|
+
run: async ({ session_id }, ctx2) => json(await new BitPalClient(ctx2.apiKey, ctx2.apiBase).request("GET", `/v1/checkout/sessions/${session_id}/status`))
|
|
11421
11893
|
}),
|
|
11422
11894
|
tool({
|
|
11423
11895
|
name: "list_test_events",
|
|
@@ -11441,20 +11913,24 @@ function visibleTools(ctx2) {
|
|
|
11441
11913
|
|
|
11442
11914
|
// src/protocol.ts
|
|
11443
11915
|
var SERVER_NAME = "bitpal";
|
|
11444
|
-
var SERVER_VERSION = "0.1.
|
|
11916
|
+
var SERVER_VERSION = "0.1.1";
|
|
11445
11917
|
var SUPPORTED_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
11446
11918
|
var INSTRUCTIONS = [
|
|
11447
11919
|
"BitPal is a non-custodial stablecoin checkout. Use these tools to write correct BitPal integrations:",
|
|
11448
11920
|
"start with get_integration_guide for the flow you are building, use search_docs / get_doc for details,",
|
|
11449
|
-
"get_api_operation for exact request/response shapes, get_webhook_event for payloads, get_error_code for errors."
|
|
11450
|
-
"With a bp_test_ key, create_test_session + simulate_payment + list_test_events run a full test payment."
|
|
11921
|
+
"get_api_operation for exact request/response shapes, get_webhook_event for payloads, get_error_code for errors."
|
|
11451
11922
|
].join(" ");
|
|
11923
|
+
var INSTRUCTIONS_WITH_TEST_KEY = "create_test_session, simulate_payment, get_session_status and list_test_events run a full test-mode payment.";
|
|
11924
|
+
var INSTRUCTIONS_WITHOUT_TEST_KEY = "Test-mode payment tools appear when the client sends a bp_test_ API key.";
|
|
11452
11925
|
var RPC = { PARSE_ERROR: -32700, INVALID_REQUEST: -32600, METHOD_NOT_FOUND: -32601, INVALID_PARAMS: -32602, INTERNAL: -32603 };
|
|
11453
11926
|
function isRecord3(v) {
|
|
11454
11927
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11455
11928
|
}
|
|
11929
|
+
function isValidId(id) {
|
|
11930
|
+
return id === void 0 || typeof id === "string" || typeof id === "number" && Number.isInteger(id);
|
|
11931
|
+
}
|
|
11456
11932
|
function isRequest(v) {
|
|
11457
|
-
return isRecord3(v) && v.jsonrpc === "2.0" && typeof v.method === "string";
|
|
11933
|
+
return isRecord3(v) && v.jsonrpc === "2.0" && typeof v.method === "string" && isValidId(v.id);
|
|
11458
11934
|
}
|
|
11459
11935
|
var ok = (id, result) => ({ jsonrpc: "2.0", id, result });
|
|
11460
11936
|
var fail = (id, code, message) => ({ jsonrpc: "2.0", id, error: { code, message } });
|
|
@@ -11472,7 +11948,7 @@ async function handleMessage(msg, ctx2) {
|
|
|
11472
11948
|
protocolVersion,
|
|
11473
11949
|
capabilities: { tools: { listChanged: false } },
|
|
11474
11950
|
serverInfo: { name: SERVER_NAME, title: "BitPal", version: SERVER_VERSION },
|
|
11475
|
-
instructions: `${INSTRUCTIONS} Docs: ${DATA.source} (spec ${DATA.specVersion}).`
|
|
11951
|
+
instructions: `${INSTRUCTIONS} ${hasTestKey(ctx2) ? INSTRUCTIONS_WITH_TEST_KEY : INSTRUCTIONS_WITHOUT_TEST_KEY} Docs: ${DATA.source} (spec ${DATA.specVersion}).`
|
|
11476
11952
|
});
|
|
11477
11953
|
}
|
|
11478
11954
|
case "ping":
|
|
@@ -11500,10 +11976,16 @@ async function handleMessage(msg, ctx2) {
|
|
|
11500
11976
|
return fail(id, RPC.METHOD_NOT_FOUND, `Method not found: ${msg.method}`);
|
|
11501
11977
|
}
|
|
11502
11978
|
}
|
|
11979
|
+
var MAX_BATCH = 20;
|
|
11503
11980
|
async function handlePayload(payload, ctx2) {
|
|
11504
11981
|
if (Array.isArray(payload)) {
|
|
11505
11982
|
if (payload.length === 0) return fail(null, RPC.INVALID_REQUEST, "Empty batch");
|
|
11506
|
-
|
|
11983
|
+
if (payload.length > MAX_BATCH) return fail(null, RPC.INVALID_REQUEST, `Batch too large (max ${MAX_BATCH})`);
|
|
11984
|
+
const out = [];
|
|
11985
|
+
for (const m of payload) {
|
|
11986
|
+
const r = await handleMessage(m, ctx2);
|
|
11987
|
+
if (r !== null) out.push(r);
|
|
11988
|
+
}
|
|
11507
11989
|
return out.length ? out : null;
|
|
11508
11990
|
}
|
|
11509
11991
|
return handleMessage(payload, ctx2);
|
|
@@ -11540,5 +12022,5 @@ rl.on("line", (line) => {
|
|
|
11540
12022
|
pending.add(p);
|
|
11541
12023
|
});
|
|
11542
12024
|
rl.on("close", () => {
|
|
11543
|
-
void Promise.allSettled([...pending]).then(() => process.exit(0));
|
|
12025
|
+
void Promise.allSettled([...pending]).then(() => process.stdout.write("", () => process.exit(0)));
|
|
11544
12026
|
});
|