Elements SDK

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

Drop in the UMD build from our S3 host with a single <script> tag for no-build pages. (npm packages are on the way — use the script tag today.)

npm install billerapi-js

Availability

The script-tag drop-in is served from an S3 bucket over HTTPS (bb-{env}-cdn-s3-elements.s3.us-east-1.amazonaws.com). The dev and staging buckets are live now for integration testing — swap prod for dev/staging in the hostname. Production distribution ships after the embed security review. The npm packages (billerapi-js, billerapi-react) are not on the public registry yet — use the script tag above.

React bindings

Using React? Install 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 three flows

Each factory returns the same ElementHandler (open(), close(), isOpen()). Opening any flow closes the one currently in flight — only one Elements modal is ever on screen.

FlowLaunch tokenonSuccess first argUse for
.connect()link_tokenpublic_tokenLink a biller account
.addPaymentMethod()pay_tokenpayment_method_idSecurely store a card
.pay()pay_tokenpayment_attempt_idPay a bill (sandbox-first)
JavaScript
// Add a payment method (no money moves)
elements.addPaymentMethod({
  payToken: 'payt_xxx', // from POST /v1/pay/token/create
  onSuccess: (paymentMethodId, metadata) => {
    console.log('Saved card', paymentMethodId, metadata.card_brand, metadata.last_4);
  },
}).open();

// Pay a bill
elements.pay({
  payToken: 'payt_xxx',
  billId: 'bill_123', // optional — pay_token may already be bill-scoped
  onSuccess: (paymentAttemptId, metadata) => {
    console.log('Payment submitted', paymentAttemptId, metadata.bill_id);
  },
}).open();

Accept payments guide

For the full payment integration — minting a pay_token, the PCI boundary, and the Add-Payment-Method vs Pay split — see the Accept Payments 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.

client_id is never trusted from the browser

The mint endpoints derive 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.

JavaScript
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().

JavaScript
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

SDK callbacks (onSuccess, onExit) are UX hints. They run in the user's browser and can be lost to a closed tab or dropped network. Treat the matching webhook event as the authoritative record before fulfilling anything:link.completed for Connect, pay.succeeded / pay.failed for payments.

See the Webhooks guide to register an endpoint and verify signatures.

Configuration

Pass an ElementsConfig object to the constructor.

PropertyTypeRequiredDescription
clientIdstringYesYour BillerAPI client ID
environment'sandbox' | 'production'NoTarget environment (auto-detected if omitted)
baseUrlstringNoCustom base URL; per-flow hosted paths are derived from it
apiUrlstringNoCustom API URL (overrides environment default)
connectUrlstringNoExplicit Connect URL (back-compat; only short-circuits the connect flow)

Related

Was this page helpful?