Documentation Index Fetch the complete documentation index at: https://docs.ozura.com/llms.txt
Use this file to discover all available pages before exploring further.
Bank elements let you collect account numbers and routing numbers in your own form without those values ever appearing in your JavaScript or reaching your server. Each field is an isolated iframe; Ozura handles tokenization securely.
OzuraPay does not currently support bank account payments. The bank token is for use with your own ACH-capable payment processor via the Proxy .
Field Types
typeDescription accountNumber4–17 digit bank account number. Rendered as a password-type input to prevent shoulder surfing. routingNumber9-digit US ABA routing number. Validated against the standard ABA checksum algorithm.
Creating and Mounting
import { OzVault , OzError } from '@ozura/elements' ;
const vault = await OzVault . create ({
pubKey: 'YOUR_PUB_KEY' ,
sessionUrl: '/api/oz-session' ,
});
const accountEl = vault . createBankElement ( 'accountNumber' );
const routingEl = vault . createBankElement ( 'routingNumber' );
accountEl . mount ( '#account-number' );
routingEl . mount ( '#routing-number' );
With options:
const style = {
base: { fontSize: '15px' , color: '#1a1a2e' , padding: '12px 14px' },
focus: { caretColor: '#6366f1' },
placeholder: { color: '#94a3b8' },
};
const accountEl = vault . createBankElement ( 'accountNumber' , { style });
const routingEl = vault . createBankElement ( 'routingNumber' , { style });
createBankElement Options
vault . createBankElement ( type : BankElementType , options ?: ElementOptions ): OzElement
Option Type Description styleElementStyleConfigPer-element style overrides. See Styling . placeholderstringPlaceholder text shown inside the field disabledbooleanMount the field in a disabled state loadTimeoutMsnumberTimeout (ms) before loaderror fires for this element. Default: 10000.
Tokenizing
Call createBankToken() after the user has filled both fields. firstName and lastName are required — they are included in the vault record alongside the account data.
document . getElementById ( 'submit-btn' ). addEventListener ( 'click' , async ( e ) => {
e . preventDefault ();
const firstName = document . getElementById ( 'first-name' ). value . trim ();
const lastName = document . getElementById ( 'last-name' ). value . trim ();
try {
const { token , bank } = await vault . createBankToken ({ firstName , lastName });
// token — vault token, pass to your backend
// bank.last4 — last 4 digits of account number (safe to display)
// bank.routingNumberLast4 — last 4 digits of routing number (safe to display)
await fetch ( '/api/ach' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ token }),
});
} catch ( err ) {
vault . reset (); // clear fields so customer can re-enter
if ( err instanceof OzError ) {
console . error ( err . message , err . errorCode );
}
}
});
createBankToken options
vault . createBankToken ( options : BankTokenizeOptions ): Promise < BankTokenResponse >
Option Type Required Description firstNamestring✅ Account holder first name lastNamestring✅ Account holder last name
BankTokenResponse
{
token : string ; // vault token — pass to your backend
bank ?: {
last4: string ; // last 4 digits of account number
routingNumberLast4 : string ; // last 4 digits of routing number
};
}
bank.routingNumberLast4 is the last 4 digits of the routing number — sufficient for display and reconciliation. The full routing number is never exposed to your JavaScript.
Events
Bank elements emit the same events as card elements:
accountEl . on ( 'change' , ({ complete , valid , error }) => {
const wrap = document . getElementById ( 'account-wrap' );
const errMsg = document . getElementById ( 'account-error' );
wrap . classList . toggle ( 'complete' , complete && valid );
wrap . classList . toggle ( 'invalid' , ! valid && error != null );
errMsg . textContent = error ?? '' ;
});
accountEl . on ( 'focus' , () => document . getElementById ( 'account-wrap' ). classList . add ( 'focused' ));
accountEl . on ( 'blur' , () => document . getElementById ( 'account-wrap' ). classList . remove ( 'focused' ));
Error messages
Field Error Condition accountNumber"Invalid account number"Not 4–17 digits routingNumber"Invalid routing number"Not 9 digits or fails ABA checksum
Gate on vault readiness, field iframe readiness, and field completeness:
let vaultReady = false ;
const fieldReady = { accountNumber: false , routingNumber: false };
const fieldFilled = { accountNumber: false , routingNumber: false };
function updateButton () {
document . getElementById ( 'submit-btn' ). disabled =
! ( vaultReady &&
fieldReady . accountNumber && fieldReady . routingNumber &&
fieldFilled . accountNumber && fieldFilled . routingNumber );
}
const vault = await OzVault . create ({
pubKey: 'YOUR_PUB_KEY' ,
sessionUrl: '/api/oz-session' ,
onReady : () => { vaultReady = true ; updateButton (); },
});
const accountEl = vault . createBankElement ( 'accountNumber' );
const routingEl = vault . createBankElement ( 'routingNumber' );
accountEl . mount ( '#account-number' );
routingEl . mount ( '#routing-number' );
accountEl . on ( 'ready' , () => { fieldReady . accountNumber = true ; updateButton (); });
routingEl . on ( 'ready' , () => { fieldReady . routingNumber = true ; updateButton (); });
accountEl . on ( 'change' , ({ complete , valid }) => {
fieldFilled . accountNumber = complete && valid ;
updateButton ();
});
routingEl . on ( 'change' , ({ complete , valid }) => {
fieldFilled . routingNumber = complete && valid ;
updateButton ();
});
Displaying Account Info After Tokenization
The bank.last4 and bank.routingNumberLast4 values returned by createBankToken() are safe to display to the user as confirmation:
const { token , bank } = await vault . createBankToken ({ firstName , lastName });
if ( bank ) {
document . getElementById ( 'confirm-text' ). textContent =
`Account ending in ${ bank . last4 } • Routing ending in ${ bank . routingNumberLast4 } ` ;
}
Validation Details
Account number
Accepts 4–17 digits
All non-digit characters are stripped as the user types
Rendered as a password field (dots, not plain text) to prevent shoulder surfing
Routing number
Must be exactly 9 digits
Validated against the standard US ABA routing number checksum
Invalid routing numbers are flagged immediately when the 9th digit is entered
Accessing an Existing Bank Element
const existing = vault . getBankElement ( 'accountNumber' );
// returns the OzElement or null if not yet created
Teardown
// Destroy the vault and all its iframes when the component unmounts
vault . destroy ();
Full End-to-End Example (Vanilla JS + Express)
A minimal but complete bank tokenization flow: HTML form, Express session route, and a backend ACH route that receives the vault token.
Frontend (HTML + JS)
<! DOCTYPE html >
< html >
< head >
< title > ACH Payment </ title >
< style >
.field-wrap { border : 1 px solid #e5e7eb ; border-radius : 6 px ; padding : 2 px ; margin-bottom : 12 px ; }
#error { color : #ef4444 ; margin-top : 8 px ; min-height : 20 px ; }
</ style >
</ head >
< body >
< form id = "ach-form" >
< div class = "row" >
< input id = "first-name" type = "text" placeholder = "First name" autocomplete = "given-name" />
< input id = "last-name" type = "text" placeholder = "Last name" autocomplete = "family-name" />
</ div >
< div class = "field-wrap" id = "account-wrap" >< div id = "account-number" ></ div ></ div >
< div class = "field-wrap" id = "routing-wrap" >< div id = "routing-number" ></ div ></ div >
< p id = "error" ></ p >
< button id = "submit-btn" type = "submit" disabled > Save bank account </ button >
</ form >
< script type = "module" >
import { OzVault , OzError } from '/oz-elements.esm.js' ;
const errorEl = document . getElementById ( 'error' );
const submitBtn = document . getElementById ( 'submit-btn' );
let vaultReady = false ;
const fieldReady = { accountNumber: false , routingNumber: false };
const fieldFilled = { accountNumber: false , routingNumber: false };
function updateBtn () {
submitBtn . disabled = ! (
vaultReady &&
fieldReady . accountNumber && fieldReady . routingNumber &&
fieldFilled . accountNumber && fieldFilled . routingNumber
);
}
let vault ;
try {
vault = await OzVault . create ({
pubKey: 'YOUR_PUB_KEY' ,
sessionUrl: '/api/oz-session' ,
onReady : () => { vaultReady = true ; updateBtn (); },
});
} catch ( err ) {
submitBtn . textContent = 'Payment fields unavailable' ;
errorEl . textContent = 'Could not load secure fields. Please refresh.' ;
submitBtn . disabled = true ;
throw err ;
}
const accountEl = vault . createBankElement ( 'accountNumber' );
const routingEl = vault . createBankElement ( 'routingNumber' );
accountEl . mount ( '#account-number' );
routingEl . mount ( '#routing-number' );
accountEl . on ( 'ready' , () => { fieldReady . accountNumber = true ; updateBtn (); });
routingEl . on ( 'ready' , () => { fieldReady . routingNumber = true ; updateBtn (); });
accountEl . on ( 'change' , ({ complete , valid }) => { fieldFilled . accountNumber = complete && valid ; updateBtn (); });
routingEl . on ( 'change' , ({ complete , valid }) => { fieldFilled . routingNumber = complete && valid ; updateBtn (); });
document . getElementById ( 'ach-form' ). addEventListener ( 'submit' , async ( e ) => {
e . preventDefault ();
errorEl . textContent = '' ;
submitBtn . disabled = true ;
try {
const { token , bank } = await vault . createBankToken ({
firstName: document . getElementById ( 'first-name' ). value . trim (),
lastName: document . getElementById ( 'last-name' ). value . trim (),
});
const res = await fetch ( '/api/ach' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ token }),
});
if ( ! res . ok ) {
const { error } = await res . json ();
throw new Error ( error || 'Request failed' );
}
document . getElementById ( 'ach-form' ). innerHTML =
`<p>Account ending in ${ bank ?. last4 } saved successfully.</p>` ;
} catch ( err ) {
vault . reset ();
errorEl . textContent = err instanceof OzError ? err . message : 'Something went wrong.' ;
submitBtn . disabled = false ;
}
});
</ script >
</ body >
</ html >
Backend (Express)
import express from 'express' ;
import { Ozura , createSessionMiddleware } from '@ozura/elements/server' ;
const app = express ();
app . use ( express . json ());
const ozura = new Ozura ({ vaultKey: process . env . VAULT_API_KEY });
// Session route — called automatically by the frontend SDK
app . post ( '/api/oz-session' , createSessionMiddleware ( ozura ));
// ACH route — receives the vault token and stores or forwards it
app . post ( '/api/ach' , async ( req , res ) => {
const { token } = req . body ;
if ( ! token ) return res . status ( 400 ). json ({ error: 'token required' });
// Pass `token` to your ACH processor via the Vault Proxy,
// or store it for future use. The token never expires server-side.
// See: /guides/vault/proxy/overview
console . log ( 'Bank vault token:' , token );
res . json ({ ok: true });
});
app . listen ( 3000 );
Next Steps
Proxy Forward the bank token to your ACH processor.
Styling Customize colors, fonts, and states.
React Components Use OzBankCard, OzBankAccountNumber, and OzBankRoutingNumber in React.
Error Handling Handle errors from createBankToken().