Checkout
Two public endpoints power the buyer flow: one to render the choices, one to create the payment. The embed calls both for you — this is the contract underneath.
Both endpoints take a publishable key in the body and enforce the request Origin. By default the amount is read server-side from the referenced object — the browser can't set a price. Accounts may opt into open-amount checkout, where the browser supplies the amount and you verify it in your webhook.
Get checkout info
Returns the display amount, your branding, and the chain/token options the buyer can choose — everything the widget needs to draw the first screen.
Request
| Field | Notes | |
|---|---|---|
publishableKey | required | Your pk_…. |
priceId | one of | A Price (price_…). |
paymentLinkId | one of | A Payment Link (plink_…). |
sessionId | one of | A Checkout Session (cs_…). |
amount | one of | Open-amount: amount in USD cents (account must allow it). |
Response · 200
{
"mode": "live",
"amount": "29.00",
"amountCents": 2900,
"currency": "usd",
"sellerName": "Acme Inc",
"branding": { "displayName": "Acme", "logoUrl": "https://…", "color": "#251160" },
"options": [
{ "network": "base", "token": "usdc", "decimals": 6 },
{ "network": "solana", "token": "usdc", "decimals": 6 }
]
}
options is the intersection of what the Price accepts and the networks you actually hold a wallet for — so the buyer is only ever shown payable choices.
Create a payment
Once the buyer picks a chain and token, this claims a payment slot and returns the exact address and amount to pay. Rate-limited to 120/min per IP.
Request
| Field | Notes | |
|---|---|---|
publishableKey | required | Your pk_…. |
priceId / paymentLinkId / sessionId / amount | one required | What's being paid for. amount (USD cents) is the open-amount path. |
currency | optional | Only with amount; must be "usd" (the default). |
network | required | Buyer's chosen chain, e.g. "base". |
token | required | Buyer's chosen token, "usdc" or "usdt". |
ttlMinutes | optional | Minutes until the payment expires. Default 60. |
metadata | optional | String key/value object (≤20 keys, ≤2 KB), echoed back on every webhook for this payment. |
Response · 201
{
"paymentId": "pay_66f2a19b03c84d2ea1b7c4de",
"status": "PENDING",
"mode": "live",
"network": "base",
"token": "usdc",
"payTo": "0xA1b2…",
"amount": "29.00",
"amountBaseUnits": "29000000",
"decimals": 6,
"contract": "0x833589…",
"txHash": null,
"expiresAt": "2026-06-28T13:00:00Z",
"branding": { "displayName": "Acme", "logoUrl": "https://…", "color": "#251160" }
}
This is the full payment object. Hand payTo + amountBaseUnits to the buyer, then poll GET /payments/{paymentId} to watch it settle.
Creating a payment isn't idempotent — each call claims a new slot. Call it once per attempt and keep the paymentId; don't re-post to "refresh." Notable errors: 402 volume_cap_reached, 422 no_wallet, 404 price_not_found, 409 session_closed, 429 rate_limited (see Errors).
Open-amount checkout
When your account allows it (Settings → Open-amount checkout), the page can set the amount directly instead of referencing a Price — ideal for carts, donations, or any amount you compute yourself. Pass amount in USD cents; no Product or Price is needed:
{
"publishableKey": "pk_live_…",
"amount": 4900,
"currency": "usd",
"network": "base",
"token": "usdc",
"metadata": { "orderId": "order_123" }
}
From the embed, the same thing is one button: <button data-hashpay-amount="4900">Pay</button>, or HashPay.checkout({ amount: 4900, metadata }).
Verify the amount in your webhook before fulfilling. With a Price or Session, HashPay guarantees the amount server-side, so you can fulfil on payment.confirmed directly. With open-amount the browser supplies the amount, so HashPay certifies only that a payment confirmed — check amount against what the order should cost, and treat metadata as untrusted buyer input, before granting anything. Sending amount while the account is in strict mode returns 403 open_amount_disabled.
Working with metadata
Anything you put in metadata at creation comes back on the payment's webhooks. It's the cleanest way to tie a HashPay payment to your own order:
{
"publishableKey": "pk_live_…",
"priceId": "price_123",
"network": "base",
"token": "usdc",
"metadata": { "orderId": "order_123", "userId": "u_42" }
}