@bitpal/mcp 0.1.2 → 0.1.3
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/dist/stdio.js +369 -79
- package/package.json +2 -1
package/dist/stdio.js
CHANGED
|
@@ -4053,28 +4053,28 @@ var NEVER = INVALID;
|
|
|
4053
4053
|
var docs_data_default = {
|
|
4054
4054
|
source: "https://bitpal.io/docs/",
|
|
4055
4055
|
specVersion: "1.0.0",
|
|
4056
|
-
rules: "## Rules for AI coding assistants\n\nFollow these when writing a BitPal integration:\n\n1. Keep secret API keys (`bp_test_\u2026`, `bp_live_\u2026`) and the webhook secret (`whsec_\u2026`) on the server. Browsers only get the `checkout_url`.\n2. Fulfil orders from the `checkout.session.paid` webhook (signature verified) or a server-side session lookup. Never from the redirect to `success_url` or a popup message.\n3. Verify webhooks on the raw request body with `X-Webhook-Signature-256` and `X-Webhook-Timestamp`. The SDK's `verifyWebhookSignature` is async \u2014 `await` it.\n4. Webhooks are delivered at least once and can arrive out of order. Deduplicate on the body `id`; keep the furthest session state.\n5. Amounts are strings. Pass `amount` as a plain number string in the line item `currency` (`\"29\"`); never use floating-point math. `amount_total` is a 6-decimal integer string; deposit amounts use the token's decimals.\n6. Session statuses are `created`, `awaiting_deposit`, `deposit_detected`, `paid_confirmed`, `swept`, `expired`, `unresolved_underpaid`, `wrong_token`. Treat `paid_confirmed`/`swept` as paid \u2014 there is no `paid` or `settled` status value.\n7. The payment window is `expires_in_minutes` (1\u201360, default 15). A full payment up to 7 days late is still accepted (`late: true` on the webhook) \u2014 handle it.\n8. Test keys accept only testnet chain values; live keys only mainnet. Use CAIP-2 ids or short names (`base`, `arbitrum`, `bnb`, `tron`, `solana`, `polygon`, `kaia`).\n9. A self-built (headless) payment UI must call BitPal through the merchant backend (CORS); only the public session endpoint is callable from the browser.\n10. Send an `Idempotency-Key` when creating a session (`POST /v1/checkout/sessions`) so retries are safe. Other POST endpoints do not enforce it yet \u2014 check whether a first attempt succeeded before retrying.\n11. For a test payment without a wallet, use Console \u2192 Developers \u2192 Sandbox (test mode): it sends the deposit for you and shows the webhook events.",
|
|
4056
|
+
rules: "## Rules for AI coding assistants\n\nFollow these when writing a BitPal integration:\n\n1. Keep secret API keys (`bp_test_\u2026`, `bp_live_\u2026`) and the webhook secret (`whsec_\u2026`) on the server. Browsers only get the `checkout_url`.\n2. Fulfil orders from the `checkout.session.paid` webhook (signature verified) or a server-side session lookup. Never from the redirect to `success_url` or a popup message.\n3. Verify webhooks on the raw request body with `X-Webhook-Signature-256` and `X-Webhook-Timestamp`. The SDK's `verifyWebhookSignature` is async \u2014 `await` it.\n4. Webhooks are delivered at least once and can arrive out of order. Deduplicate on the body `id`; keep the furthest session state.\n5. Amounts are strings. Pass `amount` as a plain number string in the line item `currency` (`\"29\"`); never use floating-point math. `amount_total` is a 6-decimal integer string; deposit amounts use the token's decimals.\n6. Session statuses are `created`, `awaiting_deposit`, `deposit_detected`, `paid_confirmed`, `swept`, `expired`, `unresolved_underpaid`, `wrong_token`. Treat `paid_confirmed`/`swept` as paid \u2014 there is no `paid` or `settled` status value.\n7. The payment window is `expires_in_minutes` (1\u201360, default 15). A full payment up to 7 days late is still accepted (`late: true` on the webhook) \u2014 handle it, or create the session with `late_payments: \"refund\"` to have late payments refunded automatically (~7 days after the deadline).\n8. Test keys accept only testnet chain values; live keys only mainnet. Use CAIP-2 ids or short names (`base`, `arbitrum`, `bnb`, `tron`, `solana`, `polygon`, `kaia`).\n9. A self-built (headless) payment UI must call BitPal through the merchant backend (CORS); only the public session endpoint is callable from the browser.\n10. Send an `Idempotency-Key` when creating a session (`POST /v1/checkout/sessions`) so retries are safe. Other POST endpoints do not enforce it yet \u2014 check whether a first attempt succeeded before retrying.\n11. For a test payment without a wallet, use Console \u2192 Developers \u2192 Sandbox (test mode): it sends the deposit for you and shows the webhook events.\n12. There is no refund API (BitPal is non-custodial). Do not generate code that calls one. A completed payment is refunded by the merchant in Console \u2192 Sessions \u2192 Refund (send from their own wallet, record the tx); listen for `checkout.refund.recorded` and treat unknown `kind` values (e.g. `merchant`) as a refund.\n13. The session total (sum of `amount` \xD7 `quantity`) must be at least 1.00 in the session currency ($1 USDC/USDT, \xA51 JPYC) or the API returns `400 CHECKOUT_AMOUNT_TOO_LOW`. Tron needs at least $300 (testnet included); smaller totals don't offer Tron.\n14. Put your order id in session `metadata` \u2014 it comes back in every webhook's `data.metadata`. `checkout.session.deposit_detected` is optional; `paid` can arrive without it. Use `deposit_tx_hash`/`sweep_tx_hash`, not the deprecated `tx_hash`.\n15. After `claude mcp add` (or adding the BitPal MCP server to any client), restart the client / start a new session so the tools load.",
|
|
4057
4057
|
pages: [
|
|
4058
4058
|
{
|
|
4059
4059
|
slug: "ai",
|
|
4060
4060
|
title: "Use with AI",
|
|
4061
4061
|
url: "https://bitpal.io/docs/#ai",
|
|
4062
4062
|
markdownUrl: "https://bitpal.io/docs/md/ai.md",
|
|
4063
|
-
markdown: '# Use with AI\n\nGive your AI coding tool BitPal\'s docs, API spec and webhook schemas so it writes integrations against the real API \u2014 and, with a test key, runs a test payment for you.\n\n## BitPal MCP server\n\n- **Remote:** `https://mcp.bitpal.io/mcp` (Streamable HTTP). Nothing to install.\n- **Local:** `npx -y @bitpal/mcp` (stdio).\n- Docs tools work without a key. Add a `bp_test_\u2026` key to unlock test-mode tools. Live keys are rejected.\n\n| Tool | What it does |\n| --- | --- |\n| `get_integration_guide` | Integration rules plus the docs for one flow: `hosted`, `popup`, `headless`, `payment_link`, `x402`, `webhooks`. |\n| `search_docs` \xB7 `get_doc` | Search the docs; read a page as Markdown. |\n| `list_api_operations` \xB7 `get_api_operation` | REST operations with request/response schemas, from the [OpenAPI spec](/docs/openapi.json). |\n| `get_webhook_event` \xB7 `get_error_code` | Webhook payload schemas; what an error code means and how to fix it. |\n| `create_test_session` \xB7 `simulate_payment` \xB7 `get_session_status` \xB7 `list_test_events` | **Test key only.** Create a test session, send the deposit (`exact`, `underpay`, `overpay`, `wrong_token`) and read the webhook events \u2014 the same as Console \u2192 Developers \u2192 Sandbox. |\n\n## Setup\n\nReplace `bp_test_\u2026` with your test key (Console \u2192 Developers \u2192 API Keys), or leave the key out for docs only.\n\n### 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```\n\n### 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```\n\n### 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```\n\n### 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```\n\n### 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```\n\n### 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```\n\n## 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.\n\n## 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```'
|
|
4063
|
+
markdown: '# Use with AI\n\nGive your AI coding tool BitPal\'s docs, API spec and webhook schemas so it writes integrations against the real API \u2014 and, with a test key, runs a test payment for you.\n\n## BitPal MCP server\n\n- **Remote:** `https://mcp.bitpal.io/mcp` (Streamable HTTP). Nothing to install.\n- **Local:** `npx -y @bitpal/mcp` (stdio).\n- Docs tools work without a key. Add a `bp_test_\u2026` key to unlock test-mode tools. Live keys are rejected. The server checks the key with BitPal: a revoked or unknown key keeps the test-mode tools hidden and says why in the server instructions.\n\n| Tool | What it does |\n| --- | --- |\n| `get_integration_guide` | Integration rules plus the docs for one flow: `hosted`, `popup`, `headless`, `payment_link`, `x402`, `webhooks`. |\n| `search_docs` \xB7 `get_doc` | Search the docs; read a page as Markdown. |\n| `list_api_operations` \xB7 `get_api_operation` | REST operations with request/response schemas, from the [OpenAPI spec](/docs/openapi.json). |\n| `get_webhook_event` \xB7 `get_error_code` | Webhook payload schemas; what an error code means and how to fix it. |\n| `create_test_session` \xB7 `simulate_payment` \xB7 `get_session_status` \xB7 `list_test_events` | **Test key only.** Create a test session, send the deposit (`exact`, `underpay`, `overpay`, `wrong_token`) and read the webhook events \u2014 the same as Console \u2192 Developers \u2192 Sandbox. |\n\n## Setup\n\nReplace `bp_test_\u2026` with your test key (Console \u2192 Developers \u2192 API Keys), or leave the key out for docs only.\n\n> **Restart your AI client after adding the server.** MCP clients load their tool list when a session starts. In Claude Code, start a new session after `claude mcp add` (check with `/mcp`); in Cursor, VS Code, Windsurf, Claude Desktop and Codex, restart the app or reload the window. Do the same after changing the key.\n\n### 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```\n\n### 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```\n\n### 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```\n\n### 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```\n\n### 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```\n\n### 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```\n\n## 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.\n\n## 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- Session total must be at least 1.00 in the session currency (CHECKOUT_AMOUNT_TOO_LOW); Tron needs $300.\n- Webhook data carries your session metadata \u2014 put your order id there.\n- Docs: https://bitpal.io/docs/ \xB7 MCP: https://mcp.bitpal.io/mcp\n```'
|
|
4064
4064
|
},
|
|
4065
4065
|
{
|
|
4066
4066
|
slug: "api",
|
|
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", "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 "available": true,\n "unavailable_reason": "coming_soon"\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 "deposit_owner": "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 "depositOwner": null\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 ("*" \\| "checkout.session.created" \\| "checkout.session.deposit_detected" \\| "checkout.session.paid" \\| "checkout.session.settled" \\| "checkout.session.expired" \\| "checkout.session.unresolved" \\| "checkout.refund.recorded" \\| "x402.payment.resolved") | Yes | Event names to deliver (see the `webhooks` section), or `*` for every event. Unknown names are rejected with 400. (\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 ("*" \\| "checkout.session.created" \\| "checkout.session.deposit_detected" \\| "checkout.session.paid" \\| "checkout.session.settled" \\| "checkout.session.expired" \\| "checkout.session.unresolved" \\| "checkout.refund.recorded" \\| "x402.payment.resolved") | No | \u2265 1 items |\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` |'
|
|
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`. **Minimum amount:** the session total (sum of `amount \xD7 quantity`) must be at least 1.00 in the session currency (USDC/USDT: $1, JPYC: \xA51); a lower total returns 400 `CHECKOUT_AMOUNT_TOO_LOW`. Some networks also have a higher per-network minimum (Tron: currently $300, testnet included; JPYC: \xA5100) \u2014 a network whose minimum is above the total is not offered to the buyer, and requesting a deposit address on it returns 400 `CHECKOUT_BELOW_MIN_PAYMENT`. **Payout wallet:** you need a payout wallet registered in the session\'s mode (test/live). With `pay_chain`, a wallet for that network\'s type (EVM, Tron or Solana) is required. With `allowed_pay_chains` / `allowed_assets`, at least one listed network must have a wallet of its type, and the buyer is only offered networks you can receive on. Without any network restriction, any payout wallet is enough: with an EVM wallet the buyer is offered Base USDC as before; without one, the networks of the wallet types you registered (if the total is below the minimum of all of them, 400 `CHECKOUT_BELOW_MIN_PAYMENT`). No usable payout wallet returns 400 `SELLER_WALLET_NOT_CONFIGURED`.\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. The total (sum of `amount \xD7 quantity`) must be at least 1.00 in the session currency, otherwise 400 `CHECKOUT_AMOUNT_TOO_LOW`. (\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. Pre-fills the receipt form on the payment completion screen; no receipt is sent automatically \u2014 the buyer requests it there. |\n| `metadata` | map | No | Your key/value data, echoed back on the session and in webhooks (`data.metadata`). Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not returned \u2014 do not use them. |\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| `late_payments` | "accept" \\| "refund" | No | What to do with a full payment that arrives after the payment deadline. `accept` (default): accepted as a late payment for up to 7 days (`checkout.session.paid` with `late: true`). `refund`: never accepted \u2014 it is refunded automatically to the buyer about 7 days after the deadline (refund kind `late_deposit`); the non-custodial deposit contract does not allow an earlier refund. |\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 "late_payments": "accept",\n "deposit_address": "string",\n "refund_address": "string",\n "received_token": "string",\n "received_token_symbol": "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 "late_payments": "accept",\n "deposit_address": "string",\n "refund_address": "string",\n "received_token": "string",\n "received_token_symbol": "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 "available": true,\n "unavailable_reason": "coming_soon"\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 "deposit_owner": "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 "depositOwner": null\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 refunds for a session\n\nRefunds recorded for this session: automatic on-chain refunds from the deposit address (overpayment, expired underpayment, wrong token, late deposit) and refunds you sent from your own wallet and recorded in the console (`kind: merchant`). There is no API to make a refund \u2014 BitPal is non-custodial; refund a completed payment from Console \u2192 Sessions \u2192 session \u2192 Refund.\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 "from_address": "string",\n "reason_code": "string",\n "note": "string",\n "explorer_url": "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` \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. Deactivating (`status: "inactive"`) requires `reason_code` (`user_request`, `security_concern`, `rotation`, `no_longer_needed` or `other`, plus an optional `reason_note`); without it the request returns 400 `REASON_CODE_REQUIRED`. Reactivating accepts the same fields optionally. Every status change is recorded in your audit log with its reason.\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| `reason_code` | "user_request" \\| "security_concern" \\| "rotation" \\| "no_longer_needed" \\| "other" | No | |\n| `reason_note` | string | No | \u2264 500 chars |\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 ("*" \\| "checkout.session.created" \\| "checkout.session.deposit_detected" \\| "checkout.session.paid" \\| "checkout.session.settled" \\| "checkout.session.expired" \\| "checkout.session.unresolved" \\| "checkout.refund.recorded" \\| "x402.payment.resolved") | Yes | Event names to deliver (see the `webhooks` section), or `*` for every event. Unknown names are rejected with 400. (\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 ("*" \\| "checkout.session.created" \\| "checkout.session.deposit_detected" \\| "checkout.session.paid" \\| "checkout.session.settled" \\| "checkout.session.expired" \\| "checkout.session.unresolved" \\| "checkout.refund.recorded" \\| "x402.payment.resolved") | No | \u2265 1 items |\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`, `metadata`, `amount_total`, `currency`, `late` |\n| `checkout.session.deposit_detected` | Funds seen on-chain, awaiting confirmations. Optional \u2014 skipped when the first time BitPal sees the deposit it is already confirmed; then `checkout.session.paid` (or `unresolved`) arrives with no `deposit_detected` before it. Do not wait for it. | `session_id`, `metadata`, `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`, `metadata`, `amount_total`, `currency`, `late`, `tx_hash` |\n| `checkout.session.settled` | Funds swept to your payout wallet. | `session_id`, `metadata`, `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`, `metadata`, `amount_total`, `currency`, `late`, `reason`, `amount_expected`, `amount_received`, `received_token`, `tx_hash` |\n| `checkout.session.expired` | Deadline passed without full payment. | `session_id`, `metadata`, `amount_total`, `currency`, `late`, `reason`, `expired_at`, `source`, `refund_tx_hash` |\n| `checkout.refund.recorded` | A refund was made on-chain (`kind`): automatic, or recorded by you in the console (`merchant`). | `session_id`, `metadata`, `kind`, `amount`, `token`, `tx_hash`, `refund_address`, `from_address`, `reason_code` |\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 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
|
|
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 (sum of `amount \xD7 quantity`) is below 1.00 in the session currency ($1 for USDC/USDT, \xA51 for JPYC). |\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| `REFUND_CONSOLE_ONLY` | 403 | Recording a refund is console-only \u2014 there is no refund API. Refund in Console \u2192 Sessions \u2192 session \u2192 Refund. |\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",
|
|
@@ -4088,7 +4088,7 @@ var docs_data_default = {
|
|
|
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. 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.'
|
|
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**Minimum amount.** A session's total (sum of `amount \xD7 quantity`) must be at least **1.00 in the session currency** (USDC/USDT: $1, JPYC: \xA51) \u2014 a lower total returns `400 CHECKOUT_AMOUNT_TOO_LOW`. The schema can't express this (it is a rule on the sum, and `amount` alone may be `\"0\"`), so check it before calling. Per-network minimums come on top: Tron currently $300 (testnet included) and JPYC \xA5100 on every network. A network whose minimum is above the total is not offered to the buyer; requesting a deposit address on it returns `400 CHECKOUT_BELOW_MIN_PAYMENT`.\n\n## Session fields worth knowing\n\n- `deposit_tx_hash` is the buyer's payment into the deposit address; `sweep_tx_hash` is the transaction that settled it to your payout wallet (or, for a refunded or rescued session, that on-chain action).\n- `tx_hash` is **deprecated**. It belonged to an earlier payment path and is kept only for compatibility \u2014 it now returns the same value as `deposit_tx_hash`. Use `deposit_tx_hash` / `sweep_tx_hash`.\n- `received_token` is the **address** of the token contract (or Solana mint) that actually arrived \u2014 not a symbol. `received_token_symbol` gives the symbol (`USDC`, `USDT` \u2026), or `null` for a token BitPal doesn't know (for example a wrong token).\n- `payer_wallet` is the address the deposit came from (or the wallet the buyer connected), in the network's own format \u2014 Tron addresses are base58 `T\u2026`. It may be an exchange's hot wallet, so don't use it as the buyer's identity or refund destination; refunds go to `refund_address`.\n- `metadata` is returned on the session as you sent it, and is also included in every webhook's `data` (see Webhooks \u2192 Event data). Keys BitPal writes itself \u2014 `_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets` \u2014 are reserved and are left out of both; don't use them for your own data.\n\n## Buyer experience\n\n- **Receipts.** BitPal does not email the buyer automatically. After paying, the buyer can request an emailed receipt on the payment completion screen; `customer_email` only pre-fills that form. If every buyer needs a receipt or order confirmation, send it yourself from your `checkout.session.paid` handler.\n- **Status link.** `checkout_url` (`/pay/{id}?token=\u2026`) is also the buyer's status page \u2014 opening it later shows the payment's current state, and the receipt email links back to it. The `token` grants access to that page, so share the URL only with the buyer.\n- **Language.** The payment page picks its language from the buyer's browser (12 languages, English as fallback), and the buyer can switch it. You can't set the language from the session or the URL yet.\n- **Your name and logo.** The payment page does not show a merchant name or logo yet. Buyers see the line item `name`s and amounts \u2014 make item names recognisable (for example include your store name).\n- **Support.** BitPal doesn't contact your buyers. Product, order and refund questions are yours to answer (completed payments are refunded by you \u2014 see Refunds above). For payment problems \u2014 funds sent but not detected, a wrong network, a late transfer \u2014 contact BitPal support with the session id and the transaction hash.\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 by default: you get `checkout.session.paid` with `\"late\": true` and the funds settle normally. Decide whether to fulfil, or refund it from the console (see Refunds below).\n- **Don't want late payments?** Create the session with `\"late_payments\": \"refund\"`. A full payment that arrives after the deadline (`expires_at` + 90 minutes) is then never accepted: it stays in the deposit address and is refunded automatically to the buyer's refund address **about 7 days after the deadline** (refund kind `late_deposit`, `checkout.refund.recorded`). The refund can't be instant \u2014 BitPal is non-custodial and the deposit contract only lets anyone but you take the funds back after that 7-day window. A transfer sent before `expires_at` whose confirmations finish later is still accepted.\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\n**BitPal is non-custodial.** A payment goes from the buyer to a one-time deposit address and then straight to your payout wallet \u2014 BitPal never holds it and has no right to pull it back. So there is **no refund API**. There are two kinds of refund, and both fire `checkout.refund.recorded`:\n\n### 1. Automatic refunds (mismatched payments)\n\nPayments that don't match the order are returned on-chain from the deposit address to the buyer's refund address \u2014 automatically, with no action from you.\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\n### 2. Refunding a completed payment (from the console)\n\nLike an app store, a completed payment is refunded by you, in the BitPal console:\n\n1. Open **Console \u2192 Sessions \u2192 the session \u2192 Refund**. It shows the network, token, the buyer's refund address (entered at payment time) and how much is still refundable (received minus refunds so far). Partial refunds are fine.\n2. **Send the refund from your own wallet** \u2014 one click with a connected EVM wallet, or copy the address, amount and token contract (with a QR code) for Tron, Solana or any other wallet.\n3. **Record the transaction hash.** BitPal checks it on-chain (confirmed, successful, the session's token sent to the refund address, after the payment, not more than what is left) and records it. You get `checkout.refund.recorded` with `kind: \"merchant\"`, the `reason_code` you picked and `from_address`; `GET /v1/checkout/sessions/{id}/refunds` lists it.\n\nCurrent policy: you pay the network fee and the refund amount; the BitPal fee on the original payment is not returned. Recording is console-only \u2014 an API key gets `403 REFUND_CONSOLE_ONLY`. Recording the same transaction again returns the existing record.\n\nTreat `kind` as open: new values may be added. Handle unknown kinds as a generic refund."
|
|
4092
4092
|
},
|
|
4093
4093
|
{
|
|
4094
4094
|
slug: "links",
|
|
@@ -4109,14 +4109,14 @@ 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`. `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
|
|
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) \u2014 see **Payout wallets** below.\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## Payout wallets\n\nPayments settle straight to your own wallet \u2014 BitPal never holds them. You register one payout address per **network family** (VM) in **Console \u2192 Settings \u2192 Payout Wallet**. There is no API for this; it is console-only.\n\n| Network family | Address to register | Used for |\n| --- | --- | --- |\n| EVM | One `0x\u2026` address | Every EVM network you accept (Base, Arbitrum, BNB Chain, Polygon, Kaia \u2026) \u2014 the same address works on all of them. |\n| Tron | A base58 address starting with `T\u2026` | Tron payments. |\n| Solana | Your **wallet (owner) address** \u2014 not a token account (ATA) | Solana payments. |\n\n- **Test and live are separate.** Wallets are registered per mode: switch the console to TEST to register test payout wallets, and to LIVE for live ones. A test wallet is never used for live payments, or the other way round.\n- **A family without a wallet is not offered.** If the buyer can choose, networks of a family with no payout wallet in that mode are left out of the payment options. If you fix the network with `pay_chain` (or a deposit address is requested on it), the call fails with `400 SELLER_WALLET_NOT_CONFIGURED`. There is no fallback to another family or another wallet. A session created without `pay_chain` needs at least one payout wallet (any family) in that mode \u2014 if `allowed_pay_chains` / `allowed_assets` are set, a wallet for one of the listed networks\' families. Without any network restriction, a session offers Base USDC if you have an EVM wallet, otherwise the networks of the families you registered.\n- **Changing a wallet** applies to deposit addresses issued afterwards. An address that was already issued is committed to the payout wallet of that moment (the commitment is part of the address), so a payment to it still settles there.\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- **Minimum:** the session total (sum of `amount \xD7 quantity`) must be at least **1.00 in the session currency** ($1 for USDC/USDT, \xA51 for JPYC), otherwise `400 CHECKOUT_AMOUNT_TOO_LOW`. Some networks have a higher minimum \u2014 Tron currently $300 (testnet included), JPYC \xA5100 on every network; such a network is not offered for a smaller total (or `400 CHECKOUT_BELOW_MIN_PAYMENT` if you fixed it with `pay_chain`).\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 from the console \u2014 or create sessions with `"late_payments": "refund"` so late payments are refunded automatically instead ([Payment lifecycle](/docs/md/lifecycle.md)).\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. 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.)"
|
|
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, full amount, `late_payments: \"refund\"`** | Not accepted. Refunded automatically about 7 days after the deadline (`late_deposit`) \u2014 the non-custodial deposit contract does not allow an earlier refund. |\n| **Late, partial** | Refunded (`late_deposit`). |\n| **Customer return (completed payment)** | No refund API \u2014 BitPal never holds the funds. Refund in Console \u2192 Sessions \u2192 session \u2192 Refund: send from your own wallet, record the transaction, and BitPal verifies it and sends `checkout.refund.recorded` (`merchant`). You pay the gas and the refund; the BitPal fee is not returned. |\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",
|
|
@@ -4130,7 +4130,7 @@ var docs_data_default = {
|
|
|
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, 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.'
|
|
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. **Optional** \u2014 skipped when the deposit is already confirmed the first time BitPal sees it (common for a normal payment); `paid` then arrives on its own. Never wait for it. |\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`): automatic, or one you sent and recorded in the console (`merchant`). |\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` and `metadata` \u2014 the `metadata` you set when creating the session (`{}` if none), so you can map an event to your order without a lookup. BitPal\'s reserved keys (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are left out. 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) \u2014 optional event, see above |\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` \xB7 `merchant`), `amount`, `token`, `tx_hash`, `refund_address`; for `merchant` also `from_address` and `reason_code` |\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 "metadata": { "order_id": "ord_1042" }\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 "metadata": { "order_id": "ord_1041" }\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 "metadata": { "order_id": "ord_1040" }\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. Its id starts with `test_`.\n- In the Sandbox event list (and the MCP `list_test_events` tool) every event is a test-mode event. The `is_test` field does **not** mean test mode: it is `true` only for **Send Test** samples and `false` for events produced by a (simulated) payment.\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",
|
|
@@ -4153,14 +4153,14 @@ var docs_data_default = {
|
|
|
4153
4153
|
pageTitle: "Use with AI",
|
|
4154
4154
|
heading: "BitPal MCP server",
|
|
4155
4155
|
url: "https://bitpal.io/docs/#ai",
|
|
4156
|
-
text: "## BitPal MCP server\n\n- **Remote:** `https://mcp.bitpal.io/mcp` (Streamable HTTP). Nothing to install.\n- **Local:** `npx -y @bitpal/mcp` (stdio).\n- Docs tools work without a key. Add a `bp_test_\u2026` key to unlock test-mode tools. Live keys are rejected.\n\n| Tool | What it does |\n| --- | --- |\n| `get_integration_guide` | Integration rules plus the docs for one flow: `hosted`, `popup`, `headless`, `payment_link`, `x402`, `webhooks`. |\n| `search_docs` \xB7 `get_doc` | Search the docs; read a page as Markdown. |\n| `list_api_operations` \xB7 `get_api_operation` | REST operations with request/response schemas, from the [OpenAPI spec](/docs/openapi.json). |\n| `get_webhook_event` \xB7 `get_error_code` | Webhook payload schemas; what an error code means and how to fix it. |\n| `create_test_session` \xB7 `simulate_payment` \xB7 `get_session_status` \xB7 `list_test_events` | **Test key only.** Create a test session, send the deposit (`exact`, `underpay`, `overpay`, `wrong_token`) and read the webhook events \u2014 the same as Console \u2192 Developers \u2192 Sandbox. |"
|
|
4156
|
+
text: "## BitPal MCP server\n\n- **Remote:** `https://mcp.bitpal.io/mcp` (Streamable HTTP). Nothing to install.\n- **Local:** `npx -y @bitpal/mcp` (stdio).\n- Docs tools work without a key. Add a `bp_test_\u2026` key to unlock test-mode tools. Live keys are rejected. The server checks the key with BitPal: a revoked or unknown key keeps the test-mode tools hidden and says why in the server instructions.\n\n| Tool | What it does |\n| --- | --- |\n| `get_integration_guide` | Integration rules plus the docs for one flow: `hosted`, `popup`, `headless`, `payment_link`, `x402`, `webhooks`. |\n| `search_docs` \xB7 `get_doc` | Search the docs; read a page as Markdown. |\n| `list_api_operations` \xB7 `get_api_operation` | REST operations with request/response schemas, from the [OpenAPI spec](/docs/openapi.json). |\n| `get_webhook_event` \xB7 `get_error_code` | Webhook payload schemas; what an error code means and how to fix it. |\n| `create_test_session` \xB7 `simulate_payment` \xB7 `get_session_status` \xB7 `list_test_events` | **Test key only.** Create a test session, send the deposit (`exact`, `underpay`, `overpay`, `wrong_token`) and read the webhook events \u2014 the same as Console \u2192 Developers \u2192 Sandbox. |"
|
|
4157
4157
|
},
|
|
4158
4158
|
{
|
|
4159
4159
|
page: "ai",
|
|
4160
4160
|
pageTitle: "Use with AI",
|
|
4161
4161
|
heading: "Setup",
|
|
4162
4162
|
url: "https://bitpal.io/docs/#ai",
|
|
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."
|
|
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.\n\n> **Restart your AI client after adding the server.** MCP clients load their tool list when a session starts. In Claude Code, start a new session after `claude mcp add` (check with `/mcp`); in Cursor, VS Code, Windsurf, Claude Desktop and Codex, restart the app or reload the window. Do the same after changing the key."
|
|
4164
4164
|
},
|
|
4165
4165
|
{
|
|
4166
4166
|
page: "ai",
|
|
@@ -4216,7 +4216,7 @@ var docs_data_default = {
|
|
|
4216
4216
|
pageTitle: "Use with AI",
|
|
4217
4217
|
heading: "Rules for your AGENTS.md",
|
|
4218
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```'
|
|
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- Session total must be at least 1.00 in the session currency (CHECKOUT_AMOUNT_TOO_LOW); Tron needs $300.\n- Webhook data carries your session metadata \u2014 put your order id there.\n- Docs: https://bitpal.io/docs/ \xB7 MCP: https://mcp.bitpal.io/mcp\n```'
|
|
4220
4220
|
},
|
|
4221
4221
|
{
|
|
4222
4222
|
page: "api",
|
|
@@ -4244,21 +4244,21 @@ var docs_data_default = {
|
|
|
4244
4244
|
pageTitle: "API reference",
|
|
4245
4245
|
heading: "Checkout sessions \u203A `POST /v1/checkout/sessions` \u2014 Create a checkout session",
|
|
4246
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
|
|
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`. **Minimum amount:** the session total (sum of `amount \xD7 quantity`) must be at least 1.00 in the session currency (USDC/USDT: $1, JPYC: \xA51); a lower total returns 400 `CHECKOUT_AMOUNT_TOO_LOW`. Some networks also have a higher per-network minimum (Tron: currently $300, testnet included; JPYC: \xA5100) \u2014 a network whose minimum is above the total is not offered to the buyer, and requesting a deposit address on it returns 400 `CHECKOUT_BELOW_MIN_PAYMENT`. **Payout wallet:** you need a payout wallet registered in the session\'s mode (test/live). With `pay_chain`, a wallet for that network\'s type (EVM, Tron or Solana) is required. With `allowed_pay_chains` / `allowed_assets`, at least one listed network must have a wallet of its type, and the buyer is only offered networks you can receive on. Without any network restriction, any payout wallet is enough: with an EVM wallet the buyer is offered Base USDC as before; without one, the networks of the wallet types you registered (if the total is below the minimum of all of them, 400 `CHECKOUT_BELOW_MIN_PAYMENT`). No usable payout wallet returns 400 `SELLER_WALLET_NOT_CONFIGURED`.\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. The total (sum of `amount \xD7 quantity`) must be at least 1.00 in the session currency, otherwise 400 `CHECKOUT_AMOUNT_TOO_LOW`. (\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. Pre-fills the receipt form on the payment completion screen; no receipt is sent automatically \u2014 the buyer requests it there. |\n| `metadata` | map | No | Your key/value data, echoed back on the session and in webhooks (`data.metadata`). Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not returned \u2014 do not use them. |\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| `late_payments` | "accept" \\| "refund" | No | What to do with a full payment that arrives after the payment deadline. `accept` (default): accepted as a late payment for up to 7 days (`checkout.session.paid` with `late: true`). `refund`: never accepted \u2014 it is refunded automatically to the buyer about 7 days after the deadline (refund kind `late_deposit`); the non-custodial deposit contract does not allow an earlier refund. |\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
4248
|
},
|
|
4249
4249
|
{
|
|
4250
4250
|
page: "api",
|
|
4251
4251
|
pageTitle: "API reference",
|
|
4252
4252
|
heading: "Checkout sessions \u203A `GET /v1/checkout/sessions` \u2014 List checkout sessions",
|
|
4253
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).'
|
|
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 "late_payments": "accept",\n "deposit_address": "string",\n "refund_address": "string",\n "received_token": "string",\n "received_token_symbol": "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
4255
|
},
|
|
4256
4256
|
{
|
|
4257
4257
|
page: "api",
|
|
4258
4258
|
pageTitle: "API reference",
|
|
4259
4259
|
heading: "Checkout sessions \u203A `GET /v1/checkout/sessions/{id}` \u2014 Retrieve a checkout session",
|
|
4260
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).'
|
|
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 "late_payments": "accept",\n "deposit_address": "string",\n "refund_address": "string",\n "received_token": "string",\n "received_token_symbol": "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
4262
|
},
|
|
4263
4263
|
{
|
|
4264
4264
|
page: "api",
|
|
@@ -4284,9 +4284,9 @@ var docs_data_default = {
|
|
|
4284
4284
|
{
|
|
4285
4285
|
page: "api",
|
|
4286
4286
|
pageTitle: "API reference",
|
|
4287
|
-
heading: "Checkout sessions \u203A `GET /v1/checkout/sessions/{id}/refunds` \u2014 List
|
|
4287
|
+
heading: "Checkout sessions \u203A `GET /v1/checkout/sessions/{id}/refunds` \u2014 List refunds for a session",
|
|
4288
4288
|
url: "https://bitpal.io/docs/#api",
|
|
4289
|
-
text: '### `GET /v1/checkout/sessions/{id}/refunds` \u2014 List
|
|
4289
|
+
text: '### `GET /v1/checkout/sessions/{id}/refunds` \u2014 List refunds for a session\n\nRefunds recorded for this session: automatic on-chain refunds from the deposit address (overpayment, expired underpayment, wrong token, late deposit) and refunds you sent from your own wallet and recorded in the console (`kind: merchant`). There is no API to make a refund \u2014 BitPal is non-custodial; refund a completed payment from Console \u2192 Sessions \u2192 session \u2192 Refund.\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 "from_address": "string",\n "reason_code": "string",\n "note": "string",\n "explorer_url": "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` \xB7 `404` \u2014 see [Errors](/docs/md/errors.md).'
|
|
4290
4290
|
},
|
|
4291
4291
|
{
|
|
4292
4292
|
page: "api",
|
|
@@ -4391,7 +4391,7 @@ var docs_data_default = {
|
|
|
4391
4391
|
pageTitle: "API reference",
|
|
4392
4392
|
heading: "Payment links \u203A `PATCH /v1/checkout/payment-links/{id}` \u2014 Update a payment link",
|
|
4393
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).'
|
|
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. Deactivating (`status: "inactive"`) requires `reason_code` (`user_request`, `security_concern`, `rotation`, `no_longer_needed` or `other`, plus an optional `reason_note`); without it the request returns 400 `REASON_CODE_REQUIRED`. Reactivating accepts the same fields optionally. Every status change is recorded in your audit log with its reason.\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| `reason_code` | "user_request" \\| "security_concern" \\| "rotation" \\| "no_longer_needed" \\| "other" | No | |\n| `reason_note` | string | No | \u2264 500 chars |\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).'
|
|
4395
4395
|
},
|
|
4396
4396
|
{
|
|
4397
4397
|
page: "api",
|
|
@@ -4489,7 +4489,7 @@ var docs_data_default = {
|
|
|
4489
4489
|
pageTitle: "API reference",
|
|
4490
4490
|
heading: "Webhook events",
|
|
4491
4491
|
url: "https://bitpal.io/docs/#api",
|
|
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` |"
|
|
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`, `metadata`, `amount_total`, `currency`, `late` |\n| `checkout.session.deposit_detected` | Funds seen on-chain, awaiting confirmations. Optional \u2014 skipped when the first time BitPal sees the deposit it is already confirmed; then `checkout.session.paid` (or `unresolved`) arrives with no `deposit_detected` before it. Do not wait for it. | `session_id`, `metadata`, `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`, `metadata`, `amount_total`, `currency`, `late`, `tx_hash` |\n| `checkout.session.settled` | Funds swept to your payout wallet. | `session_id`, `metadata`, `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`, `metadata`, `amount_total`, `currency`, `late`, `reason`, `amount_expected`, `amount_received`, `received_token`, `tx_hash` |\n| `checkout.session.expired` | Deadline passed without full payment. | `session_id`, `metadata`, `amount_total`, `currency`, `late`, `reason`, `expired_at`, `source`, `refund_tx_hash` |\n| `checkout.refund.recorded` | A refund was made on-chain (`kind`): automatic, or recorded by you in the console (`merchant`). | `session_id`, `metadata`, `kind`, `amount`, `token`, `tx_hash`, `refund_address`, `from_address`, `reason_code` |\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` |"
|
|
4493
4493
|
},
|
|
4494
4494
|
{
|
|
4495
4495
|
page: "errors",
|
|
@@ -4517,7 +4517,7 @@ var docs_data_default = {
|
|
|
4517
4517
|
pageTitle: "Errors",
|
|
4518
4518
|
heading: "Common error codes",
|
|
4519
4519
|
url: "https://bitpal.io/docs/#errors",
|
|
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
|
|
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 (sum of `amount \xD7 quantity`) is below 1.00 in the session currency ($1 for USDC/USDT, \xA51 for JPYC). |\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| `REFUND_CONSOLE_ONLY` | 403 | Recording a refund is console-only \u2014 there is no refund API. Refund in Console \u2192 Sessions \u2192 session \u2192 Refund. |\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`. |"
|
|
4521
4521
|
},
|
|
4522
4522
|
{
|
|
4523
4523
|
page: "errors",
|
|
@@ -4573,21 +4573,49 @@ var docs_data_default = {
|
|
|
4573
4573
|
pageTitle: "Payment lifecycle",
|
|
4574
4574
|
heading: "Amounts",
|
|
4575
4575
|
url: "https://bitpal.io/docs/#lifecycle",
|
|
4576
|
-
text: '## 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.'
|
|
4576
|
+
text: '## 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**Minimum amount.** A session\'s total (sum of `amount \xD7 quantity`) must be at least **1.00 in the session currency** (USDC/USDT: $1, JPYC: \xA51) \u2014 a lower total returns `400 CHECKOUT_AMOUNT_TOO_LOW`. The schema can\'t express this (it is a rule on the sum, and `amount` alone may be `"0"`), so check it before calling. Per-network minimums come on top: Tron currently $300 (testnet included) and JPYC \xA5100 on every network. A network whose minimum is above the total is not offered to the buyer; requesting a deposit address on it returns `400 CHECKOUT_BELOW_MIN_PAYMENT`.'
|
|
4577
|
+
},
|
|
4578
|
+
{
|
|
4579
|
+
page: "lifecycle",
|
|
4580
|
+
pageTitle: "Payment lifecycle",
|
|
4581
|
+
heading: "Session fields worth knowing",
|
|
4582
|
+
url: "https://bitpal.io/docs/#lifecycle",
|
|
4583
|
+
text: "## Session fields worth knowing\n\n- `deposit_tx_hash` is the buyer's payment into the deposit address; `sweep_tx_hash` is the transaction that settled it to your payout wallet (or, for a refunded or rescued session, that on-chain action).\n- `tx_hash` is **deprecated**. It belonged to an earlier payment path and is kept only for compatibility \u2014 it now returns the same value as `deposit_tx_hash`. Use `deposit_tx_hash` / `sweep_tx_hash`.\n- `received_token` is the **address** of the token contract (or Solana mint) that actually arrived \u2014 not a symbol. `received_token_symbol` gives the symbol (`USDC`, `USDT` \u2026), or `null` for a token BitPal doesn't know (for example a wrong token).\n- `payer_wallet` is the address the deposit came from (or the wallet the buyer connected), in the network's own format \u2014 Tron addresses are base58 `T\u2026`. It may be an exchange's hot wallet, so don't use it as the buyer's identity or refund destination; refunds go to `refund_address`.\n- `metadata` is returned on the session as you sent it, and is also included in every webhook's `data` (see Webhooks \u2192 Event data). Keys BitPal writes itself \u2014 `_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets` \u2014 are reserved and are left out of both; don't use them for your own data."
|
|
4584
|
+
},
|
|
4585
|
+
{
|
|
4586
|
+
page: "lifecycle",
|
|
4587
|
+
pageTitle: "Payment lifecycle",
|
|
4588
|
+
heading: "Buyer experience",
|
|
4589
|
+
url: "https://bitpal.io/docs/#lifecycle",
|
|
4590
|
+
text: "## Buyer experience\n\n- **Receipts.** BitPal does not email the buyer automatically. After paying, the buyer can request an emailed receipt on the payment completion screen; `customer_email` only pre-fills that form. If every buyer needs a receipt or order confirmation, send it yourself from your `checkout.session.paid` handler.\n- **Status link.** `checkout_url` (`/pay/{id}?token=\u2026`) is also the buyer's status page \u2014 opening it later shows the payment's current state, and the receipt email links back to it. The `token` grants access to that page, so share the URL only with the buyer.\n- **Language.** The payment page picks its language from the buyer's browser (12 languages, English as fallback), and the buyer can switch it. You can't set the language from the session or the URL yet.\n- **Your name and logo.** The payment page does not show a merchant name or logo yet. Buyers see the line item `name`s and amounts \u2014 make item names recognisable (for example include your store name).\n- **Support.** BitPal doesn't contact your buyers. Product, order and refund questions are yours to answer (completed payments are refunded by you \u2014 see Refunds above). For payment problems \u2014 funds sent but not detected, a wrong network, a late transfer \u2014 contact BitPal support with the session id and the transaction hash."
|
|
4577
4591
|
},
|
|
4578
4592
|
{
|
|
4579
4593
|
page: "lifecycle",
|
|
4580
4594
|
pageTitle: "Payment lifecycle",
|
|
4581
4595
|
heading: "Payment window and late payments",
|
|
4582
4596
|
url: "https://bitpal.io/docs/#lifecycle",
|
|
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
|
|
4597
|
+
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 by default: you get `checkout.session.paid` with `"late": true` and the funds settle normally. Decide whether to fulfil, or refund it from the console (see Refunds below).\n- **Don\'t want late payments?** Create the session with `"late_payments": "refund"`. A full payment that arrives after the deadline (`expires_at` + 90 minutes) is then never accepted: it stays in the deposit address and is refunded automatically to the buyer\'s refund address **about 7 days after the deadline** (refund kind `late_deposit`, `checkout.refund.recorded`). The refund can\'t be instant \u2014 BitPal is non-custodial and the deposit contract only lets anyone but you take the funds back after that 7-day window. A transfer sent before `expires_at` whose confirmations finish later is still accepted.\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.'
|
|
4584
4598
|
},
|
|
4585
4599
|
{
|
|
4586
4600
|
page: "lifecycle",
|
|
4587
4601
|
pageTitle: "Payment lifecycle",
|
|
4588
4602
|
heading: "Refunds",
|
|
4589
4603
|
url: "https://bitpal.io/docs/#lifecycle",
|
|
4590
|
-
text: "## Refunds\n\
|
|
4604
|
+
text: "## Refunds\n\n**BitPal is non-custodial.** A payment goes from the buyer to a one-time deposit address and then straight to your payout wallet \u2014 BitPal never holds it and has no right to pull it back. So there is **no refund API**. There are two kinds of refund, and both fire `checkout.refund.recorded`:"
|
|
4605
|
+
},
|
|
4606
|
+
{
|
|
4607
|
+
page: "lifecycle",
|
|
4608
|
+
pageTitle: "Payment lifecycle",
|
|
4609
|
+
heading: "Refunds \u203A 1. Automatic refunds (mismatched payments)",
|
|
4610
|
+
url: "https://bitpal.io/docs/#lifecycle",
|
|
4611
|
+
text: "### 1. Automatic refunds (mismatched payments)\n\nPayments that don't match the order are returned on-chain from the deposit address to the buyer's refund address \u2014 automatically, with no action from you.\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. |"
|
|
4612
|
+
},
|
|
4613
|
+
{
|
|
4614
|
+
page: "lifecycle",
|
|
4615
|
+
pageTitle: "Payment lifecycle",
|
|
4616
|
+
heading: "Refunds \u203A 2. Refunding a completed payment (from the console)",
|
|
4617
|
+
url: "https://bitpal.io/docs/#lifecycle",
|
|
4618
|
+
text: "### 2. Refunding a completed payment (from the console)\n\nLike an app store, a completed payment is refunded by you, in the BitPal console:\n\n1. Open **Console \u2192 Sessions \u2192 the session \u2192 Refund**. It shows the network, token, the buyer's refund address (entered at payment time) and how much is still refundable (received minus refunds so far). Partial refunds are fine.\n2. **Send the refund from your own wallet** \u2014 one click with a connected EVM wallet, or copy the address, amount and token contract (with a QR code) for Tron, Solana or any other wallet.\n3. **Record the transaction hash.** BitPal checks it on-chain (confirmed, successful, the session's token sent to the refund address, after the payment, not more than what is left) and records it. You get `checkout.refund.recorded` with `kind: \"merchant\"`, the `reason_code` you picked and `from_address`; `GET /v1/checkout/sessions/{id}/refunds` lists it.\n\nCurrent policy: you pay the network fee and the refund amount; the BitPal fee on the original payment is not returned. Recording is console-only \u2014 an API key gets `403 REFUND_CONSOLE_ONLY`. Recording the same transaction again returns the existing record.\n\nTreat `kind` as open: new values may be added. Handle unknown kinds as a generic refund."
|
|
4591
4619
|
},
|
|
4592
4620
|
{
|
|
4593
4621
|
page: "links",
|
|
@@ -4650,14 +4678,21 @@ var docs_data_default = {
|
|
|
4650
4678
|
pageTitle: "Quickstart",
|
|
4651
4679
|
heading: "Before you start",
|
|
4652
4680
|
url: "https://bitpal.io/docs/#quickstart",
|
|
4653
|
-
text: "## 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."
|
|
4681
|
+
text: "## 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) \u2014 see **Payout wallets** below.\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."
|
|
4682
|
+
},
|
|
4683
|
+
{
|
|
4684
|
+
page: "quickstart",
|
|
4685
|
+
pageTitle: "Quickstart",
|
|
4686
|
+
heading: "Payout wallets",
|
|
4687
|
+
url: "https://bitpal.io/docs/#quickstart",
|
|
4688
|
+
text: "## Payout wallets\n\nPayments settle straight to your own wallet \u2014 BitPal never holds them. You register one payout address per **network family** (VM) in **Console \u2192 Settings \u2192 Payout Wallet**. There is no API for this; it is console-only.\n\n| Network family | Address to register | Used for |\n| --- | --- | --- |\n| EVM | One `0x\u2026` address | Every EVM network you accept (Base, Arbitrum, BNB Chain, Polygon, Kaia \u2026) \u2014 the same address works on all of them. |\n| Tron | A base58 address starting with `T\u2026` | Tron payments. |\n| Solana | Your **wallet (owner) address** \u2014 not a token account (ATA) | Solana payments. |\n\n- **Test and live are separate.** Wallets are registered per mode: switch the console to TEST to register test payout wallets, and to LIVE for live ones. A test wallet is never used for live payments, or the other way round.\n- **A family without a wallet is not offered.** If the buyer can choose, networks of a family with no payout wallet in that mode are left out of the payment options. If you fix the network with `pay_chain` (or a deposit address is requested on it), the call fails with `400 SELLER_WALLET_NOT_CONFIGURED`. There is no fallback to another family or another wallet. A session created without `pay_chain` needs at least one payout wallet (any family) in that mode \u2014 if `allowed_pay_chains` / `allowed_assets` are set, a wallet for one of the listed networks' families. Without any network restriction, a session offers Base USDC if you have an EVM wallet, otherwise the networks of the families you registered.\n- **Changing a wallet** applies to deposit addresses issued afterwards. An address that was already issued is committed to the payout wallet of that moment (the commitment is part of the address), so a payment to it still settles there."
|
|
4654
4689
|
},
|
|
4655
4690
|
{
|
|
4656
4691
|
page: "quickstart",
|
|
4657
4692
|
pageTitle: "Quickstart",
|
|
4658
4693
|
heading: "1. Create a checkout session",
|
|
4659
4694
|
url: "https://bitpal.io/docs/#quickstart",
|
|
4660
|
-
text: '## 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.'
|
|
4695
|
+
text: '## 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- **Minimum:** the session total (sum of `amount \xD7 quantity`) must be at least **1.00 in the session currency** ($1 for USDC/USDT, \xA51 for JPYC), otherwise `400 CHECKOUT_AMOUNT_TOO_LOW`. Some networks have a higher minimum \u2014 Tron currently $300 (testnet included), JPYC \xA5100 on every network; such a network is not offered for a smaller total (or `400 CHECKOUT_BELOW_MIN_PAYMENT` if you fixed it with `pay_chain`).\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.'
|
|
4661
4696
|
},
|
|
4662
4697
|
{
|
|
4663
4698
|
page: "quickstart",
|
|
@@ -4678,7 +4713,7 @@ var docs_data_default = {
|
|
|
4678
4713
|
pageTitle: "Quickstart",
|
|
4679
4714
|
heading: "4. Fulfil on `checkout.session.paid`",
|
|
4680
4715
|
url: "https://bitpal.io/docs/#quickstart",
|
|
4681
|
-
text:
|
|
4716
|
+
text: '## 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 from the console \u2014 or create sessions with `"late_payments": "refund"` so late payments are refunded automatically instead ([Payment lifecycle](/docs/md/lifecycle.md)).'
|
|
4682
4717
|
},
|
|
4683
4718
|
{
|
|
4684
4719
|
page: "quickstart",
|
|
@@ -4713,7 +4748,7 @@ var docs_data_default = {
|
|
|
4713
4748
|
pageTitle: "Chains & contracts",
|
|
4714
4749
|
heading: "Refunds & edge cases",
|
|
4715
4750
|
url: "https://bitpal.io/docs/#reference",
|
|
4716
|
-
text:
|
|
4751
|
+
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, full amount, `late_payments: "refund"`** | Not accepted. Refunded automatically about 7 days after the deadline (`late_deposit`) \u2014 the non-custodial deposit contract does not allow an earlier refund. |\n| **Late, partial** | Refunded (`late_deposit`). |\n| **Customer return (completed payment)** | No refund API \u2014 BitPal never holds the funds. Refund in Console \u2192 Sessions \u2192 session \u2192 Refund: send from your own wallet, record the transaction, and BitPal verifies it and sends `checkout.refund.recorded` (`merchant`). You pay the gas and the refund; the BitPal fee is not returned. |'
|
|
4717
4752
|
},
|
|
4718
4753
|
{
|
|
4719
4754
|
page: "reference",
|
|
@@ -4776,7 +4811,7 @@ var docs_data_default = {
|
|
|
4776
4811
|
pageTitle: "Webhooks",
|
|
4777
4812
|
heading: "Events",
|
|
4778
4813
|
url: "https://bitpal.io/docs/#webhooks",
|
|
4779
|
-
text: "## 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. |"
|
|
4814
|
+
text: "## 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. **Optional** \u2014 skipped when the deposit is already confirmed the first time BitPal sees it (common for a normal payment); `paid` then arrives on its own. Never wait for it. |\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`): automatic, or one you sent and recorded in the console (`merchant`). |\n| `x402.payment.resolved` | An x402 call that settled with `503` reached its outcome. **Only fires for those** \u2014 the normal path answers inline. |"
|
|
4780
4815
|
},
|
|
4781
4816
|
{
|
|
4782
4817
|
page: "webhooks",
|
|
@@ -4832,28 +4867,28 @@ var docs_data_default = {
|
|
|
4832
4867
|
pageTitle: "Webhooks",
|
|
4833
4868
|
heading: "Event data",
|
|
4834
4869
|
url: "https://bitpal.io/docs/#webhooks",
|
|
4835
|
-
text:
|
|
4870
|
+
text: "## Event data\n\nEvery `data` object has `session_id` and `metadata` \u2014 the `metadata` you set when creating the session (`{}` if none), so you can map an event to your order without a lookup. BitPal's reserved keys (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are left out. 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) \u2014 optional event, see above |\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` \xB7 `merchant`), `amount`, `token`, `tx_hash`, `refund_address`; for `merchant` also `from_address` and `reason_code` |"
|
|
4836
4871
|
},
|
|
4837
4872
|
{
|
|
4838
4873
|
page: "webhooks",
|
|
4839
4874
|
pageTitle: "Webhooks",
|
|
4840
4875
|
heading: "Event data \u203A Example: `checkout.session.paid`",
|
|
4841
4876
|
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```'
|
|
4877
|
+
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 "metadata": { "order_id": "ord_1042" }\n }\n}\n```'
|
|
4843
4878
|
},
|
|
4844
4879
|
{
|
|
4845
4880
|
page: "webhooks",
|
|
4846
4881
|
pageTitle: "Webhooks",
|
|
4847
4882
|
heading: "Event data \u203A Example: `checkout.session.unresolved` (underpaid)",
|
|
4848
4883
|
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```'
|
|
4884
|
+
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 "metadata": { "order_id": "ord_1041" }\n }\n}\n```'
|
|
4850
4885
|
},
|
|
4851
4886
|
{
|
|
4852
4887
|
page: "webhooks",
|
|
4853
4888
|
pageTitle: "Webhooks",
|
|
4854
4889
|
heading: "Event data \u203A Example: `checkout.refund.recorded`",
|
|
4855
4890
|
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```'
|
|
4891
|
+
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 "metadata": { "order_id": "ord_1040" }\n }\n}\n```'
|
|
4857
4892
|
},
|
|
4858
4893
|
{
|
|
4859
4894
|
page: "webhooks",
|
|
@@ -4867,7 +4902,7 @@ var docs_data_default = {
|
|
|
4867
4902
|
pageTitle: "Webhooks",
|
|
4868
4903
|
heading: "Testing",
|
|
4869
4904
|
url: "https://bitpal.io/docs/#webhooks",
|
|
4870
|
-
text: "## 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."
|
|
4905
|
+
text: "## Testing\n\n- **Send Test** (Console \u2192 Developers \u2192 Webhooks) posts a signed sample event to your endpoint. Its id starts with `test_`.\n- In the Sandbox event list (and the MCP `list_test_events` tool) every event is a test-mode event. The `is_test` field does **not** mean test mode: it is `true` only for **Send Test** samples and `false` for events produced by a (simulated) payment.\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."
|
|
4871
4906
|
},
|
|
4872
4907
|
{
|
|
4873
4908
|
page: "x402",
|
|
@@ -4960,7 +4995,7 @@ var docs_data_default = {
|
|
|
4960
4995
|
"CHECKOUT_AMOUNT_TOO_LOW"
|
|
4961
4996
|
],
|
|
4962
4997
|
http: "400",
|
|
4963
|
-
description: "The total is below the
|
|
4998
|
+
description: "The total (sum of `amount \xD7 quantity`) is below 1.00 in the session currency ($1 for USDC/USDT, \xA51 for JPYC)."
|
|
4964
4999
|
},
|
|
4965
5000
|
{
|
|
4966
5001
|
codes: [
|
|
@@ -5035,6 +5070,13 @@ var docs_data_default = {
|
|
|
5035
5070
|
http: "404",
|
|
5036
5071
|
description: "Wrong id, or the session belongs to the other environment."
|
|
5037
5072
|
},
|
|
5073
|
+
{
|
|
5074
|
+
codes: [
|
|
5075
|
+
"REFUND_CONSOLE_ONLY"
|
|
5076
|
+
],
|
|
5077
|
+
http: "403",
|
|
5078
|
+
description: "Recording a refund is console-only \u2014 there is no refund API. Refund in Console \u2192 Sessions \u2192 session \u2192 Refund."
|
|
5079
|
+
},
|
|
5038
5080
|
{
|
|
5039
5081
|
codes: [
|
|
5040
5082
|
"PRODUCT_NOT_FOUND",
|
|
@@ -5171,7 +5213,7 @@ var docs_data_default = {
|
|
|
5171
5213
|
"Checkout sessions"
|
|
5172
5214
|
],
|
|
5173
5215
|
summary: "Create a checkout session",
|
|
5174
|
-
description:
|
|
5216
|
+
description: "Creates 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**Minimum amount:** the session total (sum of `amount \xD7 quantity`) must be at least 1.00 in the session currency (USDC/USDT: $1, JPYC: \xA51); a lower total returns 400 `CHECKOUT_AMOUNT_TOO_LOW`. Some networks also have a higher per-network minimum (Tron: currently $300, testnet included; JPYC: \xA5100) \u2014 a network whose minimum is above the total is not offered to the buyer, and requesting a deposit address on it returns 400 `CHECKOUT_BELOW_MIN_PAYMENT`.\n\n**Payout wallet:** you need a payout wallet registered in the session's mode (test/live). With `pay_chain`, a wallet for that network's type (EVM, Tron or Solana) is required. With `allowed_pay_chains` / `allowed_assets`, at least one listed network must have a wallet of its type, and the buyer is only offered networks you can receive on. Without any network restriction, any payout wallet is enough: with an EVM wallet the buyer is offered Base USDC as before; without one, the networks of the wallet types you registered (if the total is below the minimum of all of them, 400 `CHECKOUT_BELOW_MIN_PAYMENT`). No usable payout wallet returns 400 `SELLER_WALLET_NOT_CONFIGURED`.",
|
|
5175
5217
|
security: [
|
|
5176
5218
|
{
|
|
5177
5219
|
apiKey: []
|
|
@@ -5629,8 +5671,8 @@ var docs_data_default = {
|
|
|
5629
5671
|
tags: [
|
|
5630
5672
|
"Checkout sessions"
|
|
5631
5673
|
],
|
|
5632
|
-
summary: "List
|
|
5633
|
-
description: "
|
|
5674
|
+
summary: "List refunds for a session",
|
|
5675
|
+
description: "Refunds recorded for this session: automatic on-chain refunds from the deposit address (overpayment, expired underpayment, wrong token, late deposit) and refunds you sent from your own wallet and recorded in the console (`kind: merchant`). There is no API to make a refund \u2014 BitPal is non-custodial; refund a completed payment from Console \u2192 Sessions \u2192 session \u2192 Refund.",
|
|
5634
5676
|
security: [
|
|
5635
5677
|
{
|
|
5636
5678
|
apiKey: []
|
|
@@ -6538,7 +6580,7 @@ var docs_data_default = {
|
|
|
6538
6580
|
"Payment links"
|
|
6539
6581
|
],
|
|
6540
6582
|
summary: "Update a payment link",
|
|
6541
|
-
description:
|
|
6583
|
+
description: 'Operational settings and active \u2194 inactive. The price cannot change \u2014 create a new link instead. Deactivating (`status: "inactive"`) requires `reason_code` (`user_request`, `security_concern`, `rotation`, `no_longer_needed` or `other`, plus an optional `reason_note`); without it the request returns 400 `REASON_CODE_REQUIRED`. Reactivating accepts the same fields optionally. Every status change is recorded in your audit log with its reason.',
|
|
6542
6584
|
security: [
|
|
6543
6585
|
{
|
|
6544
6586
|
apiKey: []
|
|
@@ -7447,7 +7489,7 @@ var docs_data_default = {
|
|
|
7447
7489
|
"Webhook events"
|
|
7448
7490
|
],
|
|
7449
7491
|
summary: "checkout.session.deposit_detected",
|
|
7450
|
-
description: "Funds seen on-chain, awaiting confirmations.",
|
|
7492
|
+
description: "Funds seen on-chain, awaiting confirmations. Optional \u2014 skipped when the first time BitPal sees the deposit it is already confirmed; then `checkout.session.paid` (or `unresolved`) arrives with no `deposit_detected` before it. Do not wait for it.",
|
|
7451
7493
|
parameters: [
|
|
7452
7494
|
{
|
|
7453
7495
|
name: "X-Webhook-Signature-256",
|
|
@@ -7629,7 +7671,10 @@ var docs_data_default = {
|
|
|
7629
7671
|
amount_total: "1000000",
|
|
7630
7672
|
currency: "USDC",
|
|
7631
7673
|
late: false,
|
|
7632
|
-
tx_hash: "0xd9a75dfe753b20027a0a93841c4e70af1b6be061b59e530d1bc381e88c196fe3"
|
|
7674
|
+
tx_hash: "0xd9a75dfe753b20027a0a93841c4e70af1b6be061b59e530d1bc381e88c196fe3",
|
|
7675
|
+
metadata: {
|
|
7676
|
+
order_id: "ord_1042"
|
|
7677
|
+
}
|
|
7633
7678
|
}
|
|
7634
7679
|
}
|
|
7635
7680
|
}
|
|
@@ -7835,7 +7880,10 @@ var docs_data_default = {
|
|
|
7835
7880
|
amount_expected: "5000000",
|
|
7836
7881
|
amount_received: "2500000",
|
|
7837
7882
|
received_token: "0x943b94Dacf1507F2658f1CfE103Ac36Ebc13Ef65",
|
|
7838
|
-
tx_hash: "0x4461291b5d743d3950a474c015098b8cb53b4373346bd510f74481dd8dbb9d65"
|
|
7883
|
+
tx_hash: "0x4461291b5d743d3950a474c015098b8cb53b4373346bd510f74481dd8dbb9d65",
|
|
7884
|
+
metadata: {
|
|
7885
|
+
order_id: "ord_1041"
|
|
7886
|
+
}
|
|
7839
7887
|
}
|
|
7840
7888
|
}
|
|
7841
7889
|
}
|
|
@@ -7950,7 +7998,7 @@ var docs_data_default = {
|
|
|
7950
7998
|
"Webhook events"
|
|
7951
7999
|
],
|
|
7952
8000
|
summary: "checkout.refund.recorded",
|
|
7953
|
-
description: "A refund was made on-chain (`kind`).",
|
|
8001
|
+
description: "A refund was made on-chain (`kind`): automatic, or recorded by you in the console (`merchant`).",
|
|
7954
8002
|
parameters: [
|
|
7955
8003
|
{
|
|
7956
8004
|
name: "X-Webhook-Signature-256",
|
|
@@ -8038,7 +8086,10 @@ var docs_data_default = {
|
|
|
8038
8086
|
amount: "1000000",
|
|
8039
8087
|
token: "0x943b94Dacf1507F2658f1CfE103Ac36Ebc13Ef65",
|
|
8040
8088
|
tx_hash: "0xe943412e2be8b3e3fc6359b47a8eea0cc4de17768b069f52ebab1aacd873aec4",
|
|
8041
|
-
refund_address: "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"
|
|
8089
|
+
refund_address: "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",
|
|
8090
|
+
metadata: {
|
|
8091
|
+
order_id: "ord_1040"
|
|
8092
|
+
}
|
|
8042
8093
|
}
|
|
8043
8094
|
}
|
|
8044
8095
|
}
|
|
@@ -8273,14 +8324,21 @@ var docs_data_default = {
|
|
|
8273
8324
|
session_id: {
|
|
8274
8325
|
type: "string"
|
|
8275
8326
|
},
|
|
8327
|
+
metadata: {
|
|
8328
|
+
type: "object",
|
|
8329
|
+
additionalProperties: true,
|
|
8330
|
+
description: "The `metadata` you set when creating the session (`{}` if none). Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not included."
|
|
8331
|
+
},
|
|
8276
8332
|
kind: {
|
|
8277
8333
|
type: "string",
|
|
8278
8334
|
enum: [
|
|
8279
8335
|
"overpay_excess",
|
|
8280
8336
|
"underpay_expired",
|
|
8281
8337
|
"wrong_token",
|
|
8282
|
-
"late_deposit"
|
|
8283
|
-
|
|
8338
|
+
"late_deposit",
|
|
8339
|
+
"merchant"
|
|
8340
|
+
],
|
|
8341
|
+
description: "Automatic on-chain refunds from the deposit address: `overpay_excess`, `underpay_expired`, `wrong_token`, `late_deposit`. `merchant` = you refunded a completed payment from your own wallet and recorded it in the console. More kinds may be added."
|
|
8284
8342
|
},
|
|
8285
8343
|
amount: {
|
|
8286
8344
|
type: "string",
|
|
@@ -8294,10 +8352,25 @@ var docs_data_default = {
|
|
|
8294
8352
|
},
|
|
8295
8353
|
refund_address: {
|
|
8296
8354
|
type: "string"
|
|
8355
|
+
},
|
|
8356
|
+
from_address: {
|
|
8357
|
+
type: "string",
|
|
8358
|
+
description: "kind `merchant` only: the wallet the refund was sent from."
|
|
8359
|
+
},
|
|
8360
|
+
reason_code: {
|
|
8361
|
+
type: "string",
|
|
8362
|
+
enum: [
|
|
8363
|
+
"customer_request",
|
|
8364
|
+
"duplicate",
|
|
8365
|
+
"product_issue",
|
|
8366
|
+
"other"
|
|
8367
|
+
],
|
|
8368
|
+
description: "kind `merchant` only: the reason selected in the console."
|
|
8297
8369
|
}
|
|
8298
8370
|
},
|
|
8299
8371
|
required: [
|
|
8300
8372
|
"session_id",
|
|
8373
|
+
"metadata",
|
|
8301
8374
|
"kind",
|
|
8302
8375
|
"amount",
|
|
8303
8376
|
"token",
|
|
@@ -8366,7 +8439,8 @@ var docs_data_default = {
|
|
|
8366
8439
|
type: [
|
|
8367
8440
|
"string",
|
|
8368
8441
|
"null"
|
|
8369
|
-
]
|
|
8442
|
+
],
|
|
8443
|
+
description: "Sender of the deposit (or the wallet the buyer connected), in the network\u2019s address format (Tron: base58 `T\u2026`). May be an exchange hot wallet."
|
|
8370
8444
|
},
|
|
8371
8445
|
seller_wallet: {
|
|
8372
8446
|
type: "string"
|
|
@@ -8377,6 +8451,14 @@ var docs_data_default = {
|
|
|
8377
8451
|
"null"
|
|
8378
8452
|
]
|
|
8379
8453
|
},
|
|
8454
|
+
late_payments: {
|
|
8455
|
+
type: "string",
|
|
8456
|
+
enum: [
|
|
8457
|
+
"accept",
|
|
8458
|
+
"refund"
|
|
8459
|
+
],
|
|
8460
|
+
description: "What happens to a full payment that arrives after the payment deadline: `accept` (default) = accepted as a late payment for up to 7 days; `refund` = refunded automatically about 7 days after the deadline."
|
|
8461
|
+
},
|
|
8380
8462
|
deposit_address: {
|
|
8381
8463
|
type: [
|
|
8382
8464
|
"string",
|
|
@@ -8394,7 +8476,14 @@ var docs_data_default = {
|
|
|
8394
8476
|
"string",
|
|
8395
8477
|
"null"
|
|
8396
8478
|
],
|
|
8397
|
-
description: "Token contract/mint actually received at the deposit address."
|
|
8479
|
+
description: "Token contract/mint address actually received at the deposit address (an address, not the symbol \u2014 see `received_token_symbol`). Tron addresses are base58 `T\u2026`."
|
|
8480
|
+
},
|
|
8481
|
+
received_token_symbol: {
|
|
8482
|
+
type: [
|
|
8483
|
+
"string",
|
|
8484
|
+
"null"
|
|
8485
|
+
],
|
|
8486
|
+
description: "Symbol of `received_token` (e.g. `USDC`, `USDT`). Null when nothing was received yet or the token is not one BitPal knows (e.g. a wrong token)."
|
|
8398
8487
|
},
|
|
8399
8488
|
fee_amount: {
|
|
8400
8489
|
type: [
|
|
@@ -8434,19 +8523,23 @@ var docs_data_default = {
|
|
|
8434
8523
|
type: [
|
|
8435
8524
|
"string",
|
|
8436
8525
|
"null"
|
|
8437
|
-
]
|
|
8526
|
+
],
|
|
8527
|
+
description: "Deprecated: kept for compatibility and returns the same value as `deposit_tx_hash`. Use `deposit_tx_hash` (buyer payment) and `sweep_tx_hash` (settlement to your wallet).",
|
|
8528
|
+
deprecated: true
|
|
8438
8529
|
},
|
|
8439
8530
|
deposit_tx_hash: {
|
|
8440
8531
|
type: [
|
|
8441
8532
|
"string",
|
|
8442
8533
|
"null"
|
|
8443
|
-
]
|
|
8534
|
+
],
|
|
8535
|
+
description: "Transaction that paid into the deposit address."
|
|
8444
8536
|
},
|
|
8445
8537
|
sweep_tx_hash: {
|
|
8446
8538
|
type: [
|
|
8447
8539
|
"string",
|
|
8448
8540
|
"null"
|
|
8449
|
-
]
|
|
8541
|
+
],
|
|
8542
|
+
description: "Transaction that settled the funds to your payout wallet (or refunded/rescued them)."
|
|
8450
8543
|
},
|
|
8451
8544
|
success_url: {
|
|
8452
8545
|
type: [
|
|
@@ -8465,7 +8558,8 @@ var docs_data_default = {
|
|
|
8465
8558
|
"object",
|
|
8466
8559
|
"null"
|
|
8467
8560
|
],
|
|
8468
|
-
additionalProperties: true
|
|
8561
|
+
additionalProperties: true,
|
|
8562
|
+
description: "The `metadata` you set when creating the session. Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not included."
|
|
8469
8563
|
},
|
|
8470
8564
|
payment_source: {
|
|
8471
8565
|
type: [
|
|
@@ -8579,9 +8673,11 @@ var docs_data_default = {
|
|
|
8579
8673
|
"payer_wallet",
|
|
8580
8674
|
"seller_wallet",
|
|
8581
8675
|
"tolerance_percent",
|
|
8676
|
+
"late_payments",
|
|
8582
8677
|
"deposit_address",
|
|
8583
8678
|
"refund_address",
|
|
8584
8679
|
"received_token",
|
|
8680
|
+
"received_token_symbol",
|
|
8585
8681
|
"fee_amount",
|
|
8586
8682
|
"fee_rate",
|
|
8587
8683
|
"net_amount",
|
|
@@ -8610,6 +8706,11 @@ var docs_data_default = {
|
|
|
8610
8706
|
type: "string",
|
|
8611
8707
|
description: "cs_\u2026"
|
|
8612
8708
|
},
|
|
8709
|
+
metadata: {
|
|
8710
|
+
type: "object",
|
|
8711
|
+
additionalProperties: true,
|
|
8712
|
+
description: "The `metadata` you set when creating the session (`{}` if none). Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not included."
|
|
8713
|
+
},
|
|
8613
8714
|
amount_total: {
|
|
8614
8715
|
type: "string",
|
|
8615
8716
|
description: '6-decimal integer string ("1000000" = 1).'
|
|
@@ -8624,6 +8725,7 @@ var docs_data_default = {
|
|
|
8624
8725
|
},
|
|
8625
8726
|
required: [
|
|
8626
8727
|
"session_id",
|
|
8728
|
+
"metadata",
|
|
8627
8729
|
"amount_total",
|
|
8628
8730
|
"currency"
|
|
8629
8731
|
],
|
|
@@ -8636,6 +8738,11 @@ var docs_data_default = {
|
|
|
8636
8738
|
type: "string",
|
|
8637
8739
|
description: "cs_\u2026"
|
|
8638
8740
|
},
|
|
8741
|
+
metadata: {
|
|
8742
|
+
type: "object",
|
|
8743
|
+
additionalProperties: true,
|
|
8744
|
+
description: "The `metadata` you set when creating the session (`{}` if none). Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not included."
|
|
8745
|
+
},
|
|
8639
8746
|
amount_total: {
|
|
8640
8747
|
type: "string",
|
|
8641
8748
|
description: '6-decimal integer string ("1000000" = 1).'
|
|
@@ -8656,11 +8763,12 @@ var docs_data_default = {
|
|
|
8656
8763
|
},
|
|
8657
8764
|
received_token: {
|
|
8658
8765
|
type: "string",
|
|
8659
|
-
description: "Token contract address."
|
|
8766
|
+
description: "Token contract / mint address actually received (not the symbol)."
|
|
8660
8767
|
}
|
|
8661
8768
|
},
|
|
8662
8769
|
required: [
|
|
8663
8770
|
"session_id",
|
|
8771
|
+
"metadata",
|
|
8664
8772
|
"amount_total",
|
|
8665
8773
|
"currency",
|
|
8666
8774
|
"tx_hash",
|
|
@@ -8676,6 +8784,11 @@ var docs_data_default = {
|
|
|
8676
8784
|
type: "string",
|
|
8677
8785
|
description: "cs_\u2026"
|
|
8678
8786
|
},
|
|
8787
|
+
metadata: {
|
|
8788
|
+
type: "object",
|
|
8789
|
+
additionalProperties: true,
|
|
8790
|
+
description: "The `metadata` you set when creating the session (`{}` if none). Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not included."
|
|
8791
|
+
},
|
|
8679
8792
|
amount_total: {
|
|
8680
8793
|
type: "string",
|
|
8681
8794
|
description: '6-decimal integer string ("1000000" = 1).'
|
|
@@ -8712,6 +8825,7 @@ var docs_data_default = {
|
|
|
8712
8825
|
},
|
|
8713
8826
|
required: [
|
|
8714
8827
|
"session_id",
|
|
8828
|
+
"metadata",
|
|
8715
8829
|
"amount_total",
|
|
8716
8830
|
"currency"
|
|
8717
8831
|
],
|
|
@@ -8777,7 +8891,8 @@ var docs_data_default = {
|
|
|
8777
8891
|
type: [
|
|
8778
8892
|
"string",
|
|
8779
8893
|
"null"
|
|
8780
|
-
]
|
|
8894
|
+
],
|
|
8895
|
+
description: "Sender of the deposit (or the wallet the buyer connected), in the network\u2019s address format (Tron: base58 `T\u2026`). May be an exchange hot wallet."
|
|
8781
8896
|
},
|
|
8782
8897
|
seller_wallet: {
|
|
8783
8898
|
type: "string"
|
|
@@ -8788,6 +8903,14 @@ var docs_data_default = {
|
|
|
8788
8903
|
"null"
|
|
8789
8904
|
]
|
|
8790
8905
|
},
|
|
8906
|
+
late_payments: {
|
|
8907
|
+
type: "string",
|
|
8908
|
+
enum: [
|
|
8909
|
+
"accept",
|
|
8910
|
+
"refund"
|
|
8911
|
+
],
|
|
8912
|
+
description: "What happens to a full payment that arrives after the payment deadline: `accept` (default) = accepted as a late payment for up to 7 days; `refund` = refunded automatically about 7 days after the deadline."
|
|
8913
|
+
},
|
|
8791
8914
|
deposit_address: {
|
|
8792
8915
|
type: [
|
|
8793
8916
|
"string",
|
|
@@ -8805,7 +8928,14 @@ var docs_data_default = {
|
|
|
8805
8928
|
"string",
|
|
8806
8929
|
"null"
|
|
8807
8930
|
],
|
|
8808
|
-
description: "Token contract/mint actually received at the deposit address."
|
|
8931
|
+
description: "Token contract/mint address actually received at the deposit address (an address, not the symbol \u2014 see `received_token_symbol`). Tron addresses are base58 `T\u2026`."
|
|
8932
|
+
},
|
|
8933
|
+
received_token_symbol: {
|
|
8934
|
+
type: [
|
|
8935
|
+
"string",
|
|
8936
|
+
"null"
|
|
8937
|
+
],
|
|
8938
|
+
description: "Symbol of `received_token` (e.g. `USDC`, `USDT`). Null when nothing was received yet or the token is not one BitPal knows (e.g. a wrong token)."
|
|
8809
8939
|
},
|
|
8810
8940
|
fee_amount: {
|
|
8811
8941
|
type: [
|
|
@@ -8845,19 +8975,23 @@ var docs_data_default = {
|
|
|
8845
8975
|
type: [
|
|
8846
8976
|
"string",
|
|
8847
8977
|
"null"
|
|
8848
|
-
]
|
|
8978
|
+
],
|
|
8979
|
+
description: "Deprecated: kept for compatibility and returns the same value as `deposit_tx_hash`. Use `deposit_tx_hash` (buyer payment) and `sweep_tx_hash` (settlement to your wallet).",
|
|
8980
|
+
deprecated: true
|
|
8849
8981
|
},
|
|
8850
8982
|
deposit_tx_hash: {
|
|
8851
8983
|
type: [
|
|
8852
8984
|
"string",
|
|
8853
8985
|
"null"
|
|
8854
|
-
]
|
|
8986
|
+
],
|
|
8987
|
+
description: "Transaction that paid into the deposit address."
|
|
8855
8988
|
},
|
|
8856
8989
|
sweep_tx_hash: {
|
|
8857
8990
|
type: [
|
|
8858
8991
|
"string",
|
|
8859
8992
|
"null"
|
|
8860
|
-
]
|
|
8993
|
+
],
|
|
8994
|
+
description: "Transaction that settled the funds to your payout wallet (or refunded/rescued them)."
|
|
8861
8995
|
},
|
|
8862
8996
|
success_url: {
|
|
8863
8997
|
type: [
|
|
@@ -8876,7 +9010,8 @@ var docs_data_default = {
|
|
|
8876
9010
|
"object",
|
|
8877
9011
|
"null"
|
|
8878
9012
|
],
|
|
8879
|
-
additionalProperties: true
|
|
9013
|
+
additionalProperties: true,
|
|
9014
|
+
description: "The `metadata` you set when creating the session. Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not included."
|
|
8880
9015
|
},
|
|
8881
9016
|
payment_source: {
|
|
8882
9017
|
type: [
|
|
@@ -8938,9 +9073,11 @@ var docs_data_default = {
|
|
|
8938
9073
|
"payer_wallet",
|
|
8939
9074
|
"seller_wallet",
|
|
8940
9075
|
"tolerance_percent",
|
|
9076
|
+
"late_payments",
|
|
8941
9077
|
"deposit_address",
|
|
8942
9078
|
"refund_address",
|
|
8943
9079
|
"received_token",
|
|
9080
|
+
"received_token_symbol",
|
|
8944
9081
|
"fee_amount",
|
|
8945
9082
|
"fee_rate",
|
|
8946
9083
|
"net_amount",
|
|
@@ -8969,6 +9106,11 @@ var docs_data_default = {
|
|
|
8969
9106
|
type: "string",
|
|
8970
9107
|
description: "cs_\u2026"
|
|
8971
9108
|
},
|
|
9109
|
+
metadata: {
|
|
9110
|
+
type: "object",
|
|
9111
|
+
additionalProperties: true,
|
|
9112
|
+
description: "The `metadata` you set when creating the session (`{}` if none). Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not included."
|
|
9113
|
+
},
|
|
8972
9114
|
amount_total: {
|
|
8973
9115
|
type: "string",
|
|
8974
9116
|
description: '6-decimal integer string ("1000000" = 1).'
|
|
@@ -8986,6 +9128,7 @@ var docs_data_default = {
|
|
|
8986
9128
|
},
|
|
8987
9129
|
required: [
|
|
8988
9130
|
"session_id",
|
|
9131
|
+
"metadata",
|
|
8989
9132
|
"amount_total",
|
|
8990
9133
|
"currency",
|
|
8991
9134
|
"tx_hash"
|
|
@@ -8999,6 +9142,11 @@ var docs_data_default = {
|
|
|
8999
9142
|
type: "string",
|
|
9000
9143
|
description: "cs_\u2026"
|
|
9001
9144
|
},
|
|
9145
|
+
metadata: {
|
|
9146
|
+
type: "object",
|
|
9147
|
+
additionalProperties: true,
|
|
9148
|
+
description: "The `metadata` you set when creating the session (`{}` if none). Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not included."
|
|
9149
|
+
},
|
|
9002
9150
|
amount_total: {
|
|
9003
9151
|
type: "string",
|
|
9004
9152
|
description: '6-decimal integer string ("1000000" = 1).'
|
|
@@ -9016,6 +9164,7 @@ var docs_data_default = {
|
|
|
9016
9164
|
},
|
|
9017
9165
|
required: [
|
|
9018
9166
|
"session_id",
|
|
9167
|
+
"metadata",
|
|
9019
9168
|
"amount_total",
|
|
9020
9169
|
"currency",
|
|
9021
9170
|
"sweep_tx_hash"
|
|
@@ -9050,6 +9199,11 @@ var docs_data_default = {
|
|
|
9050
9199
|
type: "string",
|
|
9051
9200
|
description: "cs_\u2026"
|
|
9052
9201
|
},
|
|
9202
|
+
metadata: {
|
|
9203
|
+
type: "object",
|
|
9204
|
+
additionalProperties: true,
|
|
9205
|
+
description: "The `metadata` you set when creating the session (`{}` if none). Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not included."
|
|
9206
|
+
},
|
|
9053
9207
|
amount_total: {
|
|
9054
9208
|
type: "string",
|
|
9055
9209
|
description: '6-decimal integer string ("1000000" = 1).'
|
|
@@ -9086,6 +9240,7 @@ var docs_data_default = {
|
|
|
9086
9240
|
},
|
|
9087
9241
|
required: [
|
|
9088
9242
|
"session_id",
|
|
9243
|
+
"metadata",
|
|
9089
9244
|
"amount_total",
|
|
9090
9245
|
"currency",
|
|
9091
9246
|
"reason"
|
|
@@ -9096,7 +9251,7 @@ var docs_data_default = {
|
|
|
9096
9251
|
type: "object",
|
|
9097
9252
|
properties: {
|
|
9098
9253
|
line_items: {
|
|
9099
|
-
description: "Raw items (`name`, `currency`, `amount` XOR `amount_atomic`) or price items (`price_id`). Do not mix the two kinds.",
|
|
9254
|
+
description: "Raw items (`name`, `currency`, `amount` XOR `amount_atomic`) or price items (`price_id`). Do not mix the two kinds. The total (sum of `amount \xD7 quantity`) must be at least 1.00 in the session currency, otherwise 400 `CHECKOUT_AMOUNT_TOO_LOW`.",
|
|
9100
9255
|
type: "array",
|
|
9101
9256
|
items: {
|
|
9102
9257
|
anyOf: [
|
|
@@ -9190,12 +9345,12 @@ var docs_data_default = {
|
|
|
9190
9345
|
pattern: "^https?://"
|
|
9191
9346
|
},
|
|
9192
9347
|
customer_email: {
|
|
9193
|
-
description: "Buyer email
|
|
9348
|
+
description: "Buyer email. Pre-fills the receipt form on the payment completion screen; no receipt is sent automatically \u2014 the buyer requests it there.",
|
|
9194
9349
|
type: "string",
|
|
9195
9350
|
format: "email"
|
|
9196
9351
|
},
|
|
9197
9352
|
metadata: {
|
|
9198
|
-
description: "Your key/value data, echoed back on the session.",
|
|
9353
|
+
description: "Your key/value data, echoed back on the session and in webhooks (`data.metadata`). Reserved keys BitPal writes itself (`_bitpal`, `payer_email`, `allowed_pay_chains`, `allowed_assets`) are not returned \u2014 do not use them.",
|
|
9199
9354
|
type: "object",
|
|
9200
9355
|
additionalProperties: true
|
|
9201
9356
|
},
|
|
@@ -9248,6 +9403,14 @@ var docs_data_default = {
|
|
|
9248
9403
|
},
|
|
9249
9404
|
minItems: 1,
|
|
9250
9405
|
maxItems: 16
|
|
9406
|
+
},
|
|
9407
|
+
late_payments: {
|
|
9408
|
+
description: "What to do with a full payment that arrives after the payment deadline. `accept` (default): accepted as a late payment for up to 7 days (`checkout.session.paid` with `late: true`). `refund`: never accepted \u2014 it is refunded automatically to the buyer about 7 days after the deadline (refund kind `late_deposit`); the non-custodial deposit contract does not allow an earlier refund.",
|
|
9409
|
+
type: "string",
|
|
9410
|
+
enum: [
|
|
9411
|
+
"accept",
|
|
9412
|
+
"refund"
|
|
9413
|
+
]
|
|
9251
9414
|
}
|
|
9252
9415
|
},
|
|
9253
9416
|
required: [
|
|
@@ -10084,13 +10247,15 @@ var docs_data_default = {
|
|
|
10084
10247
|
type: [
|
|
10085
10248
|
"string",
|
|
10086
10249
|
"null"
|
|
10087
|
-
]
|
|
10250
|
+
],
|
|
10251
|
+
description: "The buyer\u2019s payment transaction into the deposit address."
|
|
10088
10252
|
},
|
|
10089
10253
|
release_tx_hash: {
|
|
10090
10254
|
type: [
|
|
10091
10255
|
"string",
|
|
10092
10256
|
"null"
|
|
10093
|
-
]
|
|
10257
|
+
],
|
|
10258
|
+
description: "The settlement transaction to the merchant\u2019s payout wallet."
|
|
10094
10259
|
},
|
|
10095
10260
|
expires_at: {
|
|
10096
10261
|
type: [
|
|
@@ -10249,7 +10414,7 @@ var docs_data_default = {
|
|
|
10249
10414
|
properties: {
|
|
10250
10415
|
kind: {
|
|
10251
10416
|
type: "string",
|
|
10252
|
-
description: "overpay_excess | underpay_expired | wrong_token | late_deposit"
|
|
10417
|
+
description: "overpay_excess | underpay_expired | wrong_token | late_deposit | merchant"
|
|
10253
10418
|
},
|
|
10254
10419
|
amount: {
|
|
10255
10420
|
type: "string"
|
|
@@ -10412,7 +10577,7 @@ var docs_data_default = {
|
|
|
10412
10577
|
},
|
|
10413
10578
|
kind: {
|
|
10414
10579
|
type: "string",
|
|
10415
|
-
description: "overpay_excess | underpay_expired | wrong_token | late_deposit"
|
|
10580
|
+
description: "overpay_excess | underpay_expired | wrong_token | late_deposit (automatic, from the deposit address) | merchant (you refunded from your own wallet and recorded it in the console). More kinds may be added \u2014 treat unknown values as a generic refund."
|
|
10416
10581
|
},
|
|
10417
10582
|
token: {
|
|
10418
10583
|
type: "string"
|
|
@@ -10452,6 +10617,34 @@ var docs_data_default = {
|
|
|
10452
10617
|
type: "string",
|
|
10453
10618
|
format: "date-time",
|
|
10454
10619
|
description: "ISO-8601 timestamp"
|
|
10620
|
+
},
|
|
10621
|
+
from_address: {
|
|
10622
|
+
type: [
|
|
10623
|
+
"string",
|
|
10624
|
+
"null"
|
|
10625
|
+
],
|
|
10626
|
+
description: "kind `merchant` only: the wallet the refund was sent from."
|
|
10627
|
+
},
|
|
10628
|
+
reason_code: {
|
|
10629
|
+
type: [
|
|
10630
|
+
"string",
|
|
10631
|
+
"null"
|
|
10632
|
+
],
|
|
10633
|
+
description: "kind `merchant` only: customer_request | duplicate | product_issue | other."
|
|
10634
|
+
},
|
|
10635
|
+
note: {
|
|
10636
|
+
type: [
|
|
10637
|
+
"string",
|
|
10638
|
+
"null"
|
|
10639
|
+
],
|
|
10640
|
+
description: "kind `merchant` only: the note entered in the console."
|
|
10641
|
+
},
|
|
10642
|
+
explorer_url: {
|
|
10643
|
+
type: [
|
|
10644
|
+
"string",
|
|
10645
|
+
"null"
|
|
10646
|
+
],
|
|
10647
|
+
description: "Block explorer link for `tx_hash`."
|
|
10455
10648
|
}
|
|
10456
10649
|
},
|
|
10457
10650
|
required: [
|
|
@@ -10466,7 +10659,11 @@ var docs_data_default = {
|
|
|
10466
10659
|
"tx_hash",
|
|
10467
10660
|
"caip2",
|
|
10468
10661
|
"block_number",
|
|
10469
|
-
"created_at"
|
|
10662
|
+
"created_at",
|
|
10663
|
+
"from_address",
|
|
10664
|
+
"reason_code",
|
|
10665
|
+
"note",
|
|
10666
|
+
"explorer_url"
|
|
10470
10667
|
]
|
|
10471
10668
|
},
|
|
10472
10669
|
UpdatePaymentLinkRequest: {
|
|
@@ -10573,6 +10770,20 @@ var docs_data_default = {
|
|
|
10573
10770
|
metadata: {
|
|
10574
10771
|
type: "object",
|
|
10575
10772
|
additionalProperties: true
|
|
10773
|
+
},
|
|
10774
|
+
reason_code: {
|
|
10775
|
+
type: "string",
|
|
10776
|
+
enum: [
|
|
10777
|
+
"user_request",
|
|
10778
|
+
"security_concern",
|
|
10779
|
+
"rotation",
|
|
10780
|
+
"no_longer_needed",
|
|
10781
|
+
"other"
|
|
10782
|
+
]
|
|
10783
|
+
},
|
|
10784
|
+
reason_note: {
|
|
10785
|
+
type: "string",
|
|
10786
|
+
maxLength: 500
|
|
10576
10787
|
}
|
|
10577
10788
|
},
|
|
10578
10789
|
additionalProperties: false
|
|
@@ -11861,7 +12072,10 @@ var actionTools = [
|
|
|
11861
12072
|
inputSchema: {
|
|
11862
12073
|
type: "object",
|
|
11863
12074
|
properties: {
|
|
11864
|
-
amount: {
|
|
12075
|
+
amount: {
|
|
12076
|
+
type: "string",
|
|
12077
|
+
description: 'Plain number string in the token currency, e.g. "10". Minimum 1.00 (USDC/USDT $1, JPYC \xA51); smaller amounts fail with CHECKOUT_AMOUNT_TOO_LOW. Some networks need more (Tron: $300 incl. testnet, JPYC: \xA5100).'
|
|
12078
|
+
},
|
|
11865
12079
|
token: { type: "string", description: "USDC, USDT or JPYC.", default: "USDC" },
|
|
11866
12080
|
chain: { type: "string", description: "Testnet CAIP-2 id or short name, e.g. eip155:84532 or base.", default: "eip155:84532" },
|
|
11867
12081
|
name: { type: "string", default: "Test order" },
|
|
@@ -11896,15 +12110,31 @@ var actionTools = [
|
|
|
11896
12110
|
}),
|
|
11897
12111
|
tool({
|
|
11898
12112
|
name: "simulate_payment",
|
|
11899
|
-
description:
|
|
12113
|
+
description: `Simulate the buyer's deposit for a TEST-mode session (testnet tokens are minted to the deposit address): exact (pay in full), underpay (half), overpay (+1 token, excess refunded) or wrong_token. The buyer refund address defaults to BitPal's test buyer wallet (not your payout wallet), so refunds go back to a "buyer". Requires a bp_test_ API key.`,
|
|
11900
12114
|
inputSchema: {
|
|
11901
12115
|
type: "object",
|
|
11902
|
-
properties: {
|
|
12116
|
+
properties: {
|
|
12117
|
+
session_id: { type: "string" },
|
|
12118
|
+
scenario: { type: "string", enum: [...SCENARIOS], default: "exact" },
|
|
12119
|
+
refund_address: {
|
|
12120
|
+
type: "string",
|
|
12121
|
+
description: "Optional buyer refund address in the chosen network's format (EVM 0x\u2026, Tron T\u2026, Solana base58). Only used when this call issues the deposit address; defaults to BitPal's test buyer wallet."
|
|
12122
|
+
}
|
|
12123
|
+
},
|
|
11903
12124
|
required: ["session_id"]
|
|
11904
12125
|
},
|
|
11905
|
-
schema: external_exports.object({
|
|
12126
|
+
schema: external_exports.object({
|
|
12127
|
+
session_id: external_exports.string().regex(/^cs_[A-Za-z0-9_-]+$/),
|
|
12128
|
+
scenario: external_exports.enum(SCENARIOS).default("exact"),
|
|
12129
|
+
refund_address: external_exports.string().trim().min(1).max(128).optional()
|
|
12130
|
+
}),
|
|
11906
12131
|
requiresTestKey: true,
|
|
11907
|
-
run: async ({ session_id, scenario }, ctx2) => json(
|
|
12132
|
+
run: async ({ session_id, scenario, refund_address }, ctx2) => json(
|
|
12133
|
+
await new BitPalClient(ctx2.apiKey, ctx2.apiBase).request("POST", `/v1/sandbox/sessions/${session_id}/simulate`, {
|
|
12134
|
+
scenario,
|
|
12135
|
+
...refund_address ? { refund_address } : {}
|
|
12136
|
+
})
|
|
12137
|
+
)
|
|
11908
12138
|
}),
|
|
11909
12139
|
tool({
|
|
11910
12140
|
name: "get_session_status",
|
|
@@ -11916,7 +12146,7 @@ var actionTools = [
|
|
|
11916
12146
|
}),
|
|
11917
12147
|
tool({
|
|
11918
12148
|
name: "list_test_events",
|
|
11919
|
-
description:
|
|
12149
|
+
description: 'List TEST-mode webhook events exactly as your endpoint receives them, with per-endpoint delivery results. Filter by session_id. Every event here is a test-mode event; the `is_test` field does NOT mean test mode \u2014 it is true only for sample events sent with the console "Send test" button and false for events from real (simulated) payments. `checkout.session.deposit_detected` is optional and often absent: when a deposit is already confirmed the first time BitPal sees it, `paid` arrives directly. Requires a bp_test_ API key.',
|
|
11920
12150
|
inputSchema: { type: "object", properties: { session_id: { type: "string" } } },
|
|
11921
12151
|
schema: external_exports.object({ session_id: external_exports.string().regex(/^cs_[A-Za-z0-9_-]+$/).optional() }),
|
|
11922
12152
|
requiresTestKey: true,
|
|
@@ -11928,15 +12158,66 @@ var actionTools = [
|
|
|
11928
12158
|
];
|
|
11929
12159
|
var ALL_TOOLS = [...docTools, ...actionTools];
|
|
11930
12160
|
function hasTestKey(ctx2) {
|
|
11931
|
-
return !!ctx2.apiKey && ctx2.apiKey.startsWith("bp_test_");
|
|
12161
|
+
return !!ctx2.apiKey && ctx2.apiKey.startsWith("bp_test_") && !ctx2.testKeyRejected;
|
|
11932
12162
|
}
|
|
11933
12163
|
function visibleTools(ctx2) {
|
|
11934
12164
|
return ALL_TOOLS.filter((t) => !t.requiresTestKey || hasTestKey(ctx2));
|
|
11935
12165
|
}
|
|
11936
12166
|
|
|
12167
|
+
// src/key-check.ts
|
|
12168
|
+
import { createHash } from "crypto";
|
|
12169
|
+
var TTL_MS = 5 * 6e4;
|
|
12170
|
+
var TIMEOUT_MS2 = 5e3;
|
|
12171
|
+
var MAX_ENTRIES = 500;
|
|
12172
|
+
var cache = /* @__PURE__ */ new Map();
|
|
12173
|
+
function isRecord3(v) {
|
|
12174
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
12175
|
+
}
|
|
12176
|
+
function cacheKey(apiKey, apiBase) {
|
|
12177
|
+
return createHash("sha256").update(`${apiBase}
|
|
12178
|
+
${apiKey}`).digest("hex");
|
|
12179
|
+
}
|
|
12180
|
+
async function probe(apiKey, apiBase) {
|
|
12181
|
+
const ctrl = new AbortController();
|
|
12182
|
+
const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS2);
|
|
12183
|
+
try {
|
|
12184
|
+
const res = await fetch(`${apiBase.replace(/\/$/, "")}/v1/sandbox/events?limit=1`, {
|
|
12185
|
+
method: "GET",
|
|
12186
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
12187
|
+
signal: ctrl.signal
|
|
12188
|
+
});
|
|
12189
|
+
if (res.ok) return { state: "valid" };
|
|
12190
|
+
if (res.status === 401 || res.status === 403) {
|
|
12191
|
+
let message = `HTTP ${res.status}`;
|
|
12192
|
+
try {
|
|
12193
|
+
const body = await res.json();
|
|
12194
|
+
if (isRecord3(body) && typeof body.error === "string") message = body.error;
|
|
12195
|
+
} catch {
|
|
12196
|
+
}
|
|
12197
|
+
return { state: "rejected", message };
|
|
12198
|
+
}
|
|
12199
|
+
return { state: "unknown" };
|
|
12200
|
+
} catch {
|
|
12201
|
+
return { state: "unknown" };
|
|
12202
|
+
} finally {
|
|
12203
|
+
clearTimeout(timer);
|
|
12204
|
+
}
|
|
12205
|
+
}
|
|
12206
|
+
async function checkTestKey(apiKey, apiBase = DEFAULT_API_BASE, now = Date.now()) {
|
|
12207
|
+
const k = cacheKey(apiKey, apiBase);
|
|
12208
|
+
const hit = cache.get(k);
|
|
12209
|
+
if (hit && now - hit.at < TTL_MS) return hit.status;
|
|
12210
|
+
const status = await probe(apiKey, apiBase);
|
|
12211
|
+
if (status.state !== "unknown") {
|
|
12212
|
+
if (cache.size >= MAX_ENTRIES) cache.clear();
|
|
12213
|
+
cache.set(k, { status, at: now });
|
|
12214
|
+
}
|
|
12215
|
+
return status;
|
|
12216
|
+
}
|
|
12217
|
+
|
|
11937
12218
|
// src/protocol.ts
|
|
11938
12219
|
var SERVER_NAME = "bitpal";
|
|
11939
|
-
var SERVER_VERSION = "0.1.
|
|
12220
|
+
var SERVER_VERSION = "0.1.3";
|
|
11940
12221
|
var SUPPORTED_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
11941
12222
|
var INSTRUCTIONS = [
|
|
11942
12223
|
"BitPal is a non-custodial stablecoin checkout. Use these tools to write correct BitPal integrations:",
|
|
@@ -11945,15 +12226,22 @@ var INSTRUCTIONS = [
|
|
|
11945
12226
|
].join(" ");
|
|
11946
12227
|
var INSTRUCTIONS_WITH_TEST_KEY = "create_test_session, simulate_payment, get_session_status and list_test_events run a full test-mode payment.";
|
|
11947
12228
|
var INSTRUCTIONS_WITHOUT_TEST_KEY = "Test-mode payment tools appear when the client sends a bp_test_ API key.";
|
|
12229
|
+
var instructionsKeyRejected = (reason) => `The bp_test_ API key was rejected by BitPal (${reason}), so the test-mode payment tools are hidden. Check the key in the BitPal console (Developers \u2192 API keys, TEST mode) \u2014 it may be revoked or from another BitPal environment \u2014 then restart the MCP client.`;
|
|
12230
|
+
async function withKeyCheck(ctx2, method) {
|
|
12231
|
+
if (!ctx2.verifyKey || !ctx2.apiKey || !ctx2.apiKey.startsWith("bp_test_")) return ctx2;
|
|
12232
|
+
if (method !== "initialize" && method !== "tools/list" && method !== "tools/call") return ctx2;
|
|
12233
|
+
const status = await checkTestKey(ctx2.apiKey, ctx2.apiBase);
|
|
12234
|
+
return status.state === "rejected" ? { ...ctx2, testKeyRejected: status.message } : ctx2;
|
|
12235
|
+
}
|
|
11948
12236
|
var RPC = { PARSE_ERROR: -32700, INVALID_REQUEST: -32600, METHOD_NOT_FOUND: -32601, INVALID_PARAMS: -32602, INTERNAL: -32603 };
|
|
11949
|
-
function
|
|
12237
|
+
function isRecord4(v) {
|
|
11950
12238
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11951
12239
|
}
|
|
11952
12240
|
function isValidId(id) {
|
|
11953
12241
|
return id === void 0 || typeof id === "string" || typeof id === "number" && Number.isInteger(id);
|
|
11954
12242
|
}
|
|
11955
12243
|
function isRequest(v) {
|
|
11956
|
-
return
|
|
12244
|
+
return isRecord4(v) && v.jsonrpc === "2.0" && typeof v.method === "string" && isValidId(v.id);
|
|
11957
12245
|
}
|
|
11958
12246
|
var ok = (id, result) => ({ jsonrpc: "2.0", id, result });
|
|
11959
12247
|
var fail = (id, code, message) => ({ jsonrpc: "2.0", id, error: { code, message } });
|
|
@@ -11961,8 +12249,9 @@ async function handleMessage(msg, ctx2) {
|
|
|
11961
12249
|
if (!isRequest(msg)) return fail(null, RPC.INVALID_REQUEST, "Invalid JSON-RPC request");
|
|
11962
12250
|
const isNotification = msg.id === void 0;
|
|
11963
12251
|
const id = msg.id ?? null;
|
|
11964
|
-
const params =
|
|
12252
|
+
const params = isRecord4(msg.params) ? msg.params : {};
|
|
11965
12253
|
if (isNotification) return null;
|
|
12254
|
+
ctx2 = await withKeyCheck(ctx2, msg.method);
|
|
11966
12255
|
switch (msg.method) {
|
|
11967
12256
|
case "initialize": {
|
|
11968
12257
|
const requested = typeof params.protocolVersion === "string" ? params.protocolVersion : "";
|
|
@@ -11971,7 +12260,7 @@ async function handleMessage(msg, ctx2) {
|
|
|
11971
12260
|
protocolVersion,
|
|
11972
12261
|
capabilities: { tools: { listChanged: false } },
|
|
11973
12262
|
serverInfo: { name: SERVER_NAME, title: "BitPal", version: SERVER_VERSION },
|
|
11974
|
-
instructions: `${INSTRUCTIONS} ${hasTestKey(ctx2) ? INSTRUCTIONS_WITH_TEST_KEY : INSTRUCTIONS_WITHOUT_TEST_KEY} Docs: ${DATA.source} (spec ${DATA.specVersion}).`
|
|
12263
|
+
instructions: `${INSTRUCTIONS} ${hasTestKey(ctx2) ? INSTRUCTIONS_WITH_TEST_KEY : ctx2.testKeyRejected ? instructionsKeyRejected(ctx2.testKeyRejected) : INSTRUCTIONS_WITHOUT_TEST_KEY} Docs: ${DATA.source} (spec ${DATA.specVersion}).`
|
|
11975
12264
|
});
|
|
11976
12265
|
}
|
|
11977
12266
|
case "ping":
|
|
@@ -11985,7 +12274,8 @@ async function handleMessage(msg, ctx2) {
|
|
|
11985
12274
|
const tool2 = visibleTools(ctx2).find((t) => t.name === name);
|
|
11986
12275
|
if (!tool2) {
|
|
11987
12276
|
const hidden = name.length > 0 && !visibleTools(ctx2).some((t) => t.name === name);
|
|
11988
|
-
|
|
12277
|
+
const why = ctx2.testKeyRejected ? `the bp_test_ API key was rejected: ${ctx2.testKeyRejected}` : "test-mode tools need a bp_test_ API key";
|
|
12278
|
+
return fail(id, RPC.INVALID_PARAMS, hidden ? `Unknown tool "${name}" (${why}).` : "Missing tool name");
|
|
11989
12279
|
}
|
|
11990
12280
|
try {
|
|
11991
12281
|
const text = await tool2.call(params.arguments, ctx2);
|
|
@@ -12015,7 +12305,7 @@ async function handlePayload(payload, ctx2) {
|
|
|
12015
12305
|
}
|
|
12016
12306
|
|
|
12017
12307
|
// src/stdio.ts
|
|
12018
|
-
var ctx = { apiKey: process.env.BITPAL_API_KEY || void 0, apiBase: process.env.BITPAL_API_BASE || DEFAULT_API_BASE };
|
|
12308
|
+
var ctx = { apiKey: process.env.BITPAL_API_KEY || void 0, apiBase: process.env.BITPAL_API_BASE || DEFAULT_API_BASE, verifyKey: true };
|
|
12019
12309
|
if (ctx.apiKey?.startsWith("bp_live_")) {
|
|
12020
12310
|
process.stderr.write("[bitpal-mcp] BITPAL_API_KEY is a live key \u2014 ignored. Only bp_test_ keys enable the test-mode tools.\n");
|
|
12021
12311
|
ctx.apiKey = void 0;
|