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

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-js

Availability

The npm packages are published as 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

npm currently serves 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

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 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.

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)
.status()discoveryRunId / linkId— (inline; onComplete)Show a run's live progress
JavaScript
// 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();

// 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 Schedule Bill 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 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.

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?