HASH PAY Docs
Docs/Reference/Checkout
API reference

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

POST/v1/checkout/infopk + origin

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

FieldNotes
publishableKeyrequiredYour pk_….
priceIdone ofA Price (price_…).
paymentLinkIdone ofA Payment Link (plink_…).
sessionIdone ofA Checkout Session (cs_…).
amountone ofOpen-amount: amount in USD cents (account must allow it).

Response · 200

responsejson
{
  "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

POST/v1/checkoutpk + origin

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

FieldNotes
publishableKeyrequiredYour pk_….
priceId / paymentLinkId / sessionId / amountone requiredWhat's being paid for. amount (USD cents) is the open-amount path.
currencyoptionalOnly with amount; must be "usd" (the default).
networkrequiredBuyer's chosen chain, e.g. "base".
tokenrequiredBuyer's chosen token, "usdc" or "usdt".
ttlMinutesoptionalMinutes until the payment expires. Default 60.
metadataoptionalString key/value object (≤20 keys, ≤2 KB), echoed back on every webhook for this payment.

Response · 201

paymentjson
{
  "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:

request bodyjson
{
  "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:

request bodyjson
{
  "publishableKey": "pk_live_…",
  "priceId": "price_123",
  "network": "base",
  "token": "usdc",
  "metadata": { "orderId": "order_123", "userId": "u_42" }
}