Skip to main content

Bank Payments — Using Elements

Ozura Elements lets you embed account number and routing number fields directly in your own page. Input fields run inside isolated iframes served from Ozura’s domain — raw bank data never leaves those iframes. Tokenization happens in the browser; you receive a vault token that your backend can use to process an ACH payment with your processor.
OzuraPay does not currently support bank account payments. The token you receive is for use with your own ACH-capable payment processor via the Proxy.
Prefer a hosted page? See Checkout (merchants only). Need to tokenize from your backend? See Using the API.

How It Works

Account and routing numbers travel directly from the browser to the Ozura Vault — your server only ever sees a token.

Install the SDK

npm install @ozura/elements

Quick Start

1. Get your credentials

Get your Vault API Key from ozuravault.com → your project → Applications. Your first vault project is a test project by default. To create a production project, use the project dropdown in the top-left → Create new project → enable the Production switch.
CredentialRequiredWhere
Vault API KeyAlwaysozuravault.com → Applications
Vault Pub KeyProduction onlyProvided when you set up a production project — contact ammar@ozura.com
Test/sandbox vault API key? No pub key needed — you can omit pubKey entirely. Production vault API key? Pass your production pub key as pubKey. Do not hardcode credentials in source code; use environment variables.See Sandbox for how to create a Test project at ozuravault.com and grab a test vault key.

2. Initialize and mount bank fields

<!DOCTYPE html>
<html>
<head>
  <script type="module">
    import { OzVault } from 'https://elements.ozura.com/oz-elements.esm.js';

    // pubKey is only required for production vault API keys.
    // Omit it entirely when using a test/sandbox vault API key.
    let vault;
    try {
      vault = await OzVault.create({
        pubKey:     'YOUR_PUB_KEY',         // production vault keys only — remove this line for test vault keys
        sessionUrl: '/api/oz-session',
      });
    } catch (err) {
      document.getElementById('submit-btn').textContent = 'Payment unavailable';
      document.getElementById('submit-btn').disabled = true;
      throw err;
    }

    // Create bank elements
    const accountEl = vault.createBankElement('accountNumber');
    const routingEl = vault.createBankElement('routingNumber');

    // Mount into your DOM
    accountEl.mount('#account-number');
    routingEl.mount('#routing-number');

    // Tokenize on submit
    document.getElementById('submit-btn').addEventListener('click', async () => {
      const firstName = document.getElementById('first-name').value;
      const lastName  = document.getElementById('last-name').value;

      const { token, bank } = await vault.createBankToken({ firstName, lastName });

      // bank.last4              — last 4 digits of account number (safe to display)
      // bank.routingNumberLast4 — last 4 digits of routing number (safe to display)

      // Send token to your backend
      await fetch('/api/ach-charge', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token }),
      });
    });
  </script>
</head>
<body>
  <input id="first-name" placeholder="First name" />
  <input id="last-name"  placeholder="Last name" />
  <div id="account-number"></div>
  <div id="routing-number"></div>
  <button id="submit-btn">Submit</button>
</body>
</html>

3. Process the payment on your backend

Your backend receives the token and forwards it to your ACH processor via the Proxy:
curl -X POST https://api.ozuravault.com/proxy/transaction \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_VAULT_API_KEY" \
  -d '{
    "token": "tok_...",
    "proxy_url": "https://api.yourachprocessor.com/payments",
    "request_data": {
      "account_number": "${accountNumber}",
      "routing_number": "${routingNumber}",
      "first_name":     "${first_name}",
      "last_name":      "${last_name}",
      "amount":         5000
    }
  }'

Token Response

createBankToken() resolves with:
{
  token: string;        // vault token — pass to your backend
  bank?: {
    last4:              string; // last 4 digits of account number (safe to display)
    routingNumberLast4: string; // last 4 digits of routing number (safe to display)
  };
}

Enabling the Submit Button

Use change events to gate your submit button until both fields are complete and valid:
const state = { accountReady: false, routingReady: false };

function updateButton() {
  document.getElementById('submit-btn').disabled =
    !(state.accountReady && state.routingReady);
}

accountEl.on('change', ({ complete, valid }) => {
  state.accountReady = complete && valid;
  updateButton();
});

routingEl.on('change', ({ complete, valid }) => {
  state.routingReady = complete && valid;
  updateButton();
});
Elements validate in real-time:
  • Account number — 4–17 digits; masked like a password field to prevent shoulder surfing
  • Routing number — Exactly 9 digits, validated against the standard ABA checksum algorithm

Field Validation & Error Handling

accountEl.on('change', ({ complete, valid, error }) => {
  const wrap   = document.getElementById('account-wrap');
  const status = document.getElementById('account-error');

  wrap.classList.toggle('complete', complete && valid);
  wrap.classList.toggle('invalid',  !valid && error != null);
  status.textContent = error ?? '';
});
Error messages returned in the error field:
  • "Invalid account number" — fewer than 4 digits or not all digits
  • "Invalid routing number" — not 9 digits or fails ABA checksum

React Integration

For most integrations, use the pre-built OzBankCard component (or the individual OzBankAccountNumber / OzBankRoutingNumber components). See the React SDK reference for the full prop API.
import { OzElements, OzBankCard, useOzElements } from '@ozura/elements/react';

function BankForm() {
  const { createBankToken, ready } = useOzElements();
  const handleSubmit = async () => {
    const { token } = await createBankToken({ firstName: 'Jane', lastName: 'Doe' });
    // send token to backend
  };
  return (
    <>
      <OzBankCard />
      <button disabled={!ready} onClick={handleSubmit}>Link account</button>
    </>
  );
}

// Wrap in the provider. Omit `pubKey` for test vault keys.
export function BankPage() {
  return (
    <OzElements pubKey="YOUR_PUB_KEY" sessionUrl="/api/oz-session">
      <BankForm />
    </OzElements>
  );
}
For custom layouts where the pre-built components don’t fit, use the vanilla OzVault API inside a useEffect to create and mount bank elements manually:
import { useEffect, useRef } from 'react';
import { OzVault } from '@ozura/elements';

export function BankForm() {
  const vaultRef       = useRef<OzVault | null>(null);
  const accountWrapRef = useRef<HTMLDivElement>(null);
  const routingWrapRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let vault: OzVault | null = null;
    let cancelled = false;

    // Omit `pubKey` when using a test vault key from a Test project at ozuravault.com.
    OzVault.create({
      pubKey:     'YOUR_PUB_KEY',
      sessionUrl: '/api/oz-session',
    }).then(v => {
      if (cancelled) { v.destroy(); return; }
      vault = v;
      vaultRef.current = v;

      const accountEl = v.createBankElement('accountNumber');
      const routingEl = v.createBankElement('routingNumber');

      if (accountWrapRef.current) accountEl.mount(accountWrapRef.current);
      if (routingWrapRef.current) routingEl.mount(routingWrapRef.current);
    });

    return () => { cancelled = true; vault?.destroy(); };
  }, []);

  const handleSubmit = async (firstName: string, lastName: string) => {
    if (!vaultRef.current) return;
    const { token } = await vaultRef.current.createBankToken({ firstName, lastName });
    // send token to backend
  };

  return (
    <div>
      <div ref={accountWrapRef} />
      <div ref={routingWrapRef} />
    </div>
  );
}

Styling

Bank elements accept the same style config as card elements:
const style = {
  base:        { color: '#1a1a2e', fontSize: '15px', padding: '12px 14px', backgroundColor: '#f8fafc' },
  focus:       { backgroundColor: '#ffffff', caretColor: '#6366f1' },
  invalid:     { color: '#ef4444' },
  complete:    { color: '#22c55e' },
  placeholder: { color: '#94a3b8' },
};

const accountEl = vault.createBankElement('accountNumber', { style });
const routingEl = vault.createBankElement('routingNumber', { style });
See the Styling guide for the full property reference.

Next Steps

Elements SDK Reference

Complete API reference — all methods, options, events, and TypeScript types.

Styling & Appearance

Customize fonts, colors, and states to match your brand.

Proxy

Forward the bank token to your ACH processor.

API Reference — Tokenize

Token response shape and all tokenize options.