Elements SDK
billerapi-js is the unified drop-in SDK for every BillerAPI flow — Connect, Add Payment Method, and Pay. One client, three secure iframes. Credentials and card numbers never touch your code.
Installation
Install the browser SDK from npm for bundled apps, or drop in the hosted UMD build with a single <script> tag for no-build pages.
npm install billerapi-jsAvailability
billerapi-js and billerapi-react. The script-tag drop-in is served from an S3 bucket over HTTPS (bb-{env}-cdn-s3-elements.s3.us-east-1.amazonaws.com). Use npm for app bundles and the script tag for no-build prototypes. External customer rollout still follows your account's go-live and embed-security gates.Reviewed 3.x release is not published
billerapi-js@1.0.1 and billerapi-react@1.0.1. The reviewed next release intent is the coordinated 3.0.0 pair. Keep existing elements/v1 and immutable 1.x CDN objects unchanged until a separately authorized exact-SHA npm/CDN release.React bindings
billerapi-react for the <ElementsProvider> component and the useConnect, useAddPaymentMethod, and usePay hooks.Quick Start
Construct one BillerApiElements instance with your client ID, then launch any flow with a server-minted token.
import { BillerApiElements } from 'billerapi-js';
// 1. One client for every flow
const elements = new BillerApiElements({
clientId: 'your_client_id',
environment: 'sandbox',
});
// 2. Launch the Connect flow with a link_token from your server
const handler = elements.connect({
linkToken: 'lt_xxx', // from POST /v1/link-tokens
onSuccess: (publicToken, metadata) => {
// Send publicToken to your server to exchange for an access token
console.log('Linked!', metadata.account_id, metadata.institution_name);
},
onExit: (error) => {
if (error) console.error('Connect error:', error.code, error.message);
},
});
// 3. Open the secure iframe
handler.open();The four flows
The three modal factories return the same ElementHandler (open(), close(), isOpen()). Opening any flow closes the one currently in flight — only one Elements modal is ever on screen. .status() is different: it renders inline (mount()/unmount()) and is read-only — it shows an end-user the live progress of a run they kicked off, and never collects data. The .pay() factory is reserved: it returns 501 PAYMENT_EXECUTION_NOT_AVAILABLE in sandbox and production and does not create an attempt.
| Flow | Launch token | onSuccess first arg | Use for |
|---|---|---|---|
| .connect() | link_token | public_token | Link a biller account |
| .addPaymentMethod() | pay_token | payment_method_id | Securely store a card |
| .pay() | pay_token | No success value | Unavailable; returns 501 PAYMENT_EXECUTION_NOT_AVAILABLE |
| .status() | discoveryRunId / linkId | — (inline; onComplete) | Show a run's live progress |
// Add a payment method (no money moves)
elements.addPaymentMethod({
payToken: 'payt_xxx', // from POST /v1/pay-tokens
onSuccess: (paymentMethodId, metadata) => {
console.log('Saved card', paymentMethodId, metadata.card_brand, metadata.last_4);
},
}).open();
// Bill-payment execution is unavailable in sandbox and production.
// The reserved .pay() flow returns 501 PAYMENT_EXECUTION_NOT_AVAILABLE.Accept payments guide
pay_token, the PCI boundary, and the Add-Payment-Method vs Pay split — see the Store Payment Methods guide.Server-side token minting
Every flow launches with a short-lived, scoped token minted on your server using your client credentials. The browser only ever holds the token — your API key never reaches the client. Connect uses a link_token; payment flows use a pay_token.
# Connect flow — link_token (24h TTL)
curl -X POST https://sandbox.api.billerapi.com/v1/link-tokens \
-H "Authorization: Bearer $BILLERAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "client_user_id": "user_42" }'
# => { "link_token": "sb_link_tok_...", "expires_at": "..." }client_id is never trusted from the browser
client_id from your authenticated request, not the request body. Tokens are scoped to a singleclient_user_id (and optional bill / amount) and expire quickly — mint one per flow launch, server-side.Theming
Pass 'light', 'dark', or a theme object to any flow. The SDK accepts camelCase and normalizes it to the snake_case wire shape before handing it to the hosted page.
elements.connect({
linkToken: 'lt_xxx',
theme: {
primaryColor: '#2B6CB0',
mode: 'system', // 'light' | 'dark' | 'system'
borderRadius: 'md', // 'sm' | 'md' | 'lg'
clientLogoUrl: 'https://yourapp.com/logo.svg',
},
onSuccess: (publicToken) => { /* ... */ },
});Error handling
onExit fires both on user-cancel (no argument) and on error (an ElementsError with code and message). A missing or expired launch token is reported as MODAL_OPEN_ERROR rather than throwing from open().
elements.connect({
linkToken,
onSuccess: (publicToken) => { /* ... */ },
onExit: (error) => {
if (!error) {
// user closed the modal — not an error
return;
}
switch (error.code) {
case 'MODAL_OPEN_ERROR':
// missing/expired token — mint a fresh one and retry
break;
default:
console.error(error.code, error.message);
}
},
});Webhooks are the source of truth
Confirm outcomes server-side
onSuccess, onExit) are UX hints. They run in the user's browser and can be lost to a closed tab or dropped network. For Connect, treat the signed link.completed webhook as the authoritative record before provisioning linked-account access. Pay execution is unavailable and produces no payment attempt or pay.* event. pay.succeeded and pay.failed are reserved future event names only; do not build fulfillment against them until Pay launches.See the Webhook Confirmations guide for the exact callback-to-webhook reconciliation path, and the Webhooks guide to register an endpoint and verify signatures.
Configuration
Pass an ElementsConfig object to the constructor.
| Property | Type | Required | Description |
|---|---|---|---|
| clientId | string | Yes | Your BillerAPI client ID |
| environment | 'sandbox' | 'production' | No | Target environment (auto-detected if omitted) |
| baseUrl | string | No | Custom base URL; per-flow hosted paths are derived from it |
| apiUrl | string | No | Custom API URL (overrides environment default) |
| connectUrl | string | No | Explicit Connect URL (back-compat; only short-circuits the connect flow) |