Pay Sessions
pay_tokens to launch the Elements add-payment-method and pay flows. Your backend mints a pay token (client-authenticated), hands it to the hosted Elements iframe, and the iframe validates it to read its scope before rendering.Related guides: Accept Payments · Elements SDK
The Pay Token object
A pay token is a short-lived, single-purpose credential your backend mints (client-authenticated) and hands to the hosted Elements iframe. It is the browser's only credential — your client secret never reaches the frontend, and card PAN/CVV never leave the BillerAPI-hosted page.
Attributes
| pay_token*string | The pay token used to initialize the Elements add-payment-method / pay flows |
| pay_token_id*string | Unique identifier for the pay token |
| client_id*string | Your client ID (authoritative — taken from the credential, never the body) |
| client_user_id*string | Your unique identifier for the end user |
| statusstring | AWAITING_PAYMENT_METHOD | AWAITING_PAYMENT | COMPLETED | EXPIRED |
| bill_idstring | Bill the token is scoped to (required to run a payment) |
| amount_valueinteger | Amount in minor units (cents); 0 when unscoped |
| amount_currencystring | ISO 4217 currency code, e.g. "USD" |
| expires_atstring | ISO 8601 timestamp of when the token expires (15-minute TTL) |
{
"pay_token": "payt_5f3c…",
"pay_token_id": "ptid_789xyz",
"client_id": "your_client_id",
"client_user_id": "user_12345",
"status": "AWAITING_PAYMENT",
"bill_id": "bill_abc123",
"amount_value": 1299,
"amount_currency": "USD",
"expires_at": "2026-06-29T12:15:00Z"
}/v1/pay/token/createServer-to-server. Mint a pay token for the hosted Elements flows. Requires client credentials (API key / API key); the client_id is taken from the credential — a mismatched body value is rejected with 403. Tokens are short-lived (15 minutes).
Request body
| client_user_id*string | Your unique identifier for the end user |
| bill_idstring | Bill to pay (required for the pay flow; omit for add-payment-method only) |
| amountobject | { value: integer (minor units), currency: string } — the amount to authorize |
| payment_method_idstring | Pre-bind a saved payment method |
| metadata_jsonstring | Opaque JSON string echoed back on the token |
curl -X POST https://sandbox.api.billerapi.com/v1/pay/token/create \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BILLERAPI_API_KEY" \
-d '{
"client_user_id": "user_12345",
"bill_id": "bill_abc123",
"amount": { "value": 1299, "currency": "USD" }
}'{
"success": true,
"pay_token": "payt_5f3c…",
"pay_token_id": "ptid_789xyz",
"expires_at": "2026-06-29T12:15:00Z"
}/v1/pay/token/validateUnauthenticated — the token itself is the credential (capability URL). The hosted Elements page calls this on load to confirm the token and read its scope (bill, amount, status) before rendering. You normally don't call this directly; the SDK does.
Request body
| pay_token*string | The pay token to validate |
curl -X POST https://sandbox.api.billerapi.com/v1/pay/token/validate \
-H "Content-Type: application/json" \
-d '{ "pay_token": "payt_5f3c…" }'{
"valid": true,
"pay_token_id": "ptid_789xyz",
"client_id": "your_client_id",
"client_user_id": "user_12345",
"status": "AWAITING_PAYMENT",
"bill_id": "bill_abc123",
"amount_value": 1299,
"amount_currency": "USD",
"expires_at": "2026-06-29T12:15:00Z"
}Next: launch the flow
Pass the pay_token to the Elements SDK on your frontend. Outcomes are confirmed by webhooks (pay.succeeded / pay.failed) — the SDK callbacks are UX hints.
import { BillerApiElements } from 'billerapi-js';
const elements = new BillerApiElements({ clientId: 'your_client_id', environment: 'sandbox' });
elements.pay({
payToken, // from POST /v1/pay/token/create
onSuccess: (paymentAttemptId) => console.log('paying', paymentAttemptId),
onExit: (err) => console.log('closed', err),
}).open();