Skip to main content

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.

npm / yarn

npm install @ozura/elements
Pre-release builds (@next): The next dist-tag contains unreleased changes before they reach latest. It is intended for Ozura-coordinated testing only — production credentials will not work with it. If you have been given access to pre-release testing, contact ammar@ozura.com for the matching staging credentials and setup instructions.
npm install @ozura/elements@next
npm install @ozura/elements (no tag) always resolves latest@next has no impact on production installs.
Then import in your JavaScript or TypeScript:
import { OzVault, OzError } from '@ozura/elements';
For React components:
import {
  OzElements,
  useOzElements,
  OzCard,
  OzCardNumber,
  OzExpiry,
  OzCvv,
  OzBankCard,
  OzBankAccountNumber,
  OzBankRoutingNumber,
} from '@ozura/elements/react';
@ozura/elements/react requires React 17 or later as a peer dependency. If React is not already in your project, install it first:
npm install react react-dom
For server-side usage (Node.js, Deno, Bun):
import { Ozura, OzuraError, getClientIp, createCardSaleHandler, createSessionHandler } from '@ozura/elements/server';

CDN (Script Tag)

The Ozura CDN blocks localhost origins. If you load the CDN script on http://localhost, the browser will reject it with a CORS error (No 'Access-Control-Allow-Origin' header). The CDN is for deployed origins only. See Local Development below.
If you’re not using a bundler, load the UMD build directly from the Ozura CDN:
<script src="https://elements.ozura.com/oz-elements.umd.js"></script>
<script>
  const { OzVault } = window.OzElements;

  OzVault.create({
    pubKey:     'YOUR_PUB_KEY',
    sessionUrl: '/api/oz-session',
  }).then(vault => {
    // vault is ready
  });
</script>
Or use the ESM build with a native module script:
<script type="module">
  import { OzVault } from 'https://elements.ozura.com/oz-elements.esm.js';

  const vault = await OzVault.create({
    pubKey:     'YOUR_PUB_KEY',
    sessionUrl: '/api/oz-session',
  });
</script>

Local Development

The CDN blocks localhost. For local development you have two options depending on how you load the SDK: If your project uses a bundler (webpack, Vite, Next.js, etc.), @ozura/elements is imported directly from node_modules — no CDN involved, no workaround needed. The iframes load from elements.ozura.com at runtime, but that uses <iframe src> which is not subject to the same CORS restriction.
npm install @ozura/elements

CDN script tag on localhost

If you want to use the CDN <script> tag during local development, you need to serve the SDK file from the same origin as your page. Install the npm package and add a single route to your local server:
app.get('/oz-elements.esm.js', (_, res) =>
  res.sendFile('node_modules/@ozura/elements/dist/oz-elements.esm.js', { root: '.' })
);
Then change the <script src> in your HTML from:
<script src="https://elements.ozura.com/oz-elements.esm.js"></script>
to:
<script src="/oz-elements.esm.js"></script>
The iframes (/frame/tokenizer-frame.js, /frame/element-frame.js) still load from elements.ozura.com — they are fetched via <iframe src>, not fetch, so CORS does not apply.

TypeScript

The package ships with full TypeScript definitions — no @types package needed. Import types directly:
import type {
  OzVault,
  OzElement,
  OzError,
  VaultOptions,
  ElementOptions,
  ElementType,
  BankElementType,
  ElementStyleConfig,
  ElementChangeEvent,
  TokenizeOptions,
  TokenResponse,
  CardMetadata,
  BillingDetails,
  BillingAddress,
  BankTokenizeOptions,
  BankTokenResponse,
  BankAccountMetadata,
  FontSource,
  Appearance,
  AppearanceVariables,
  OzTheme,
  CardSaleRequest,
  CardSaleResponseData,
} from '@ozura/elements';
For server-side code:
import type {
  OzuraConfig,
  CreateSessionOptions,
  CreateSessionResult,
  CardSaleInput,
  CardSaleHandlerOptions,
  ListTransactionsInput,
  ListTransactionsResult,
} from '@ozura/elements/server';

// MintWaxKeyOptions and MintWaxKeyResult are deprecated aliases for
// CreateSessionOptions and CreateSessionResult — both still work.

Pub Key for Local Development

Production pub keys have domain restrictions and will not work on localhost. The vault rejects tokenization requests from unregistered origins. OzVault.create() will fail — and without a try/catch, the failure is silent. Use the test pub key below for all local development.
Use this pub key for local and CI environments:
pk_prod_jys4le1jgncomgda_L8HbeakKLNRWdBXoX5A6QJUYOlhUkNle
This key has no domain restrictions and is safe for localhost. Do not use it with real card data or in production. For a production pub key tied to your domain, contact ammar@ozura.com. Backend credentials for local testing: The test pub key handles frontend tokenization. For a full charge flow on localhost (hitting /api/charge with a real card sale), you need valid vault API key + merchant credentials — these are production credentials. Ozura does not currently offer a separate sandbox environment for charge flows. Contact ammar@ozura.com for guidance on local charge testing.
Frame updates are automatic and backward compatible. The SDK always loads iframe files from https://elements.ozura.com. Updates are delivered transparently — no npm update is required. If you need to pin to a specific deployment for any reason, pass an explicit frameBaseUrl.

Credentials

Which credentials do you need?
GoalRequired credentials
Tokenize card / bank data only (no charging)Vault pub key + Vault API key
Charge cards via OzuraPayAll four credentials
You need credentials from the Ozura Dashboard.
CredentialFormatWhere it livesRequired for
Vault pub keypk_live_… or pk_prod_…Frontend env var (safe to expose)All integrations
Vault API keykey_…Server env var only — never in browserCreating sessions (all integrations)
Pay API keyak_…Server env var onlyOzuraPay merchants (card charging)
Merchant IDozu_…Server env var onlyOzuraPay merchants (card charging)
If you are not routing payments through OzuraPay you only need the vault pub key (frontend) and vault API key (backend).
The vault API secret must never be sent to the browser. The SDK’s session mechanism exists specifically so your server can create a short-lived credential without exposing the secret. See the Server SDK for setup.

Quick Initialization

OzVault.create() is the public factory method — it sets up the vault and initializes the payment session. It resolves once the session is ready; you can create and mount elements immediately after. The vault is ready to tokenize once onReady fires (or vault.isReady === true).
onReady fires before OzVault.create() resolves. Vault initialization has two parallel steps — session fetch and iframe load. Either can finish first. If the iframe loads before the session resolves, onReady fires while await OzVault.create(...) is still pending and vault is still undefined. Do not reference vault inside onReady. Declare any readiness flags before calling create() so they are in scope when the callback runs.
Always wrap OzVault.create() in a try/catch. If initialization fails (e.g. pub key blocked, session endpoint unreachable, backend error) the promise rejects. Without a catch, the failure is a silent unhandled rejection and the fields appear to hang.
import { OzVault, OzError } from '@ozura/elements';

let vault;
try {
  vault = await OzVault.create({
    pubKey:     'YOUR_PUB_KEY',
    sessionUrl: '/api/oz-session',
  });
} catch (err) {
  if (err instanceof OzError) {
    // err.errorCode: 'auth' | 'network' | 'timeout' | 'config' | ...
    console.error('Vault init failed:', err.message, err.errorCode);
  }
  // Render a fallback UI — payment fields are not available
  document.getElementById('payment-section').textContent =
    'Payment fields could not load. Please refresh and try again.';
}
sessionUrl is the simplest integration option — just pass your session endpoint path and the SDK handles everything. See Server SDK — Session route for the matching backend route. To provide custom headers or auth tokens, use getSessionKey instead:
let vault;
try {
  vault = await OzVault.create({
    pubKey: 'YOUR_PUB_KEY',
    getSessionKey: async (sessionId) => {
      const res = await fetch('/api/oz-session', {
        method:  'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
        body:    JSON.stringify({ sessionId }),
      });
      if (!res.ok) throw new Error(`Session endpoint returned ${res.status}`);
      const { sessionKey } = await res.json();
      return sessionKey;
    },
  });
} catch (err) {
  console.error('Vault init failed:', err);
}

OzVault.create() Options

OzVault.create(options: VaultOptions, signal?: AbortSignal): Promise<OzVault>
OptionTypeRequiredDefaultDescription
pubKeystringYour Vault pub key
sessionUrlstring✅ ¹URL of your session endpoint — the simplest option
getSessionKey(sessionId: string) => Promise<string>✅ ¹Custom callback for session key retrieval (custom headers, auth, etc.)
fetchWaxKey(sessionId: string) => Promise<string>✅ ¹Deprecated — use sessionUrl or getSessionKey instead
frameBaseUrlstringhttps://elements.ozura.comOverride where iframe assets are served from
fontsFontSource[][]Custom web fonts to load inside iframes
appearanceAppearanceGlobal theme and CSS variable overrides
onLoadError() => voidCalled if the vault fails to load within loadTimeoutMs
loadTimeoutMsnumber10000Only relevant when onLoadError is set: ms to wait before calling onLoadError
onSessionRefresh() => voidCalled when the SDK silently refreshes the session during a tokenization attempt
onReady() => voidCalled once when the vault is ready (vanilla JS only; in React use useOzElements().ready)
sessionLimitnumber | null3Maximum card submissions per session before auto-refresh. Pass null to remove the cap. Must match sessionLimit in your server-side createSession call — see Server SDK → createSession.
debugbooleanfalseEnables [OzVault]-prefixed console.log output at every lifecycle event. Safe in production — no sensitive data is logged.
¹ Exactly one of sessionUrl, getSessionKey, or fetchWaxKey is required.
Keep sessionLimit in sync between client and server. If VaultOptions.sessionLimit on the client differs from the value passed to ozura.createSession() on your server, the proactive refresh timing will be off. The vault may reject a tokenization attempt before the client expects a refresh, causing a user-visible delay. Both default to 3 — only change one if you change the other.
The optional signal parameter is an AbortSignal for advanced teardown scenarios. The React OzElements provider handles this automatically — vanilla JS integrations rarely need it.

Content Security Policy (CSP)

If your site sets a Content-Security-Policy header, you need to allow the Ozura iframe and tokenizer origins. Without this the iframes are blocked before they load and OzVault.create() will reject. Add these directives to your CSP:
frame-src   https://elements.ozura.com;
connect-src https://elements.ozura.com;
If you use frameBaseUrl to point at a custom or staging deployment, substitute that origin for https://elements.ozura.com. If you load custom fonts via the fonts option (e.g. Google Fonts), add the font CDN origins too:
style-src  'self' https://fonts.googleapis.com;
font-src   'self' https://fonts.gstatic.com;
Full header example (Express):
app.use((_, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    [
      "default-src 'self'",
      "script-src  'self' 'nonce-YOURNONCE'",
      "frame-src   'self' https://elements.ozura.com",
      "connect-src 'self' https://elements.ozura.com",
      // Add the two lines below only if using fonts: [{ cssSrc: 'https://fonts.googleapis.com/...' }]
      "style-src   'self' https://fonts.googleapis.com",
      "font-src    'self' https://fonts.gstatic.com",
    ].join('; ')
  );
  next();
});
The connect-src entry covers requests made by the Ozura iframe for tokenization.

Next.js App Router

When using Next.js with the App Router (app/ directory), the vault and card fields require a client component boundary because they use browser APIs.

Client component wrapper

// app/checkout/CardForm.tsx
'use client';

import { useEffect, useRef } from 'react';
import { OzVault, OzError } from '@ozura/elements';

export function CardForm() {
  const cardNumberRef = useRef<HTMLDivElement>(null);
  const expiryRef     = useRef<HTMLDivElement>(null);
  const cvvRef        = useRef<HTMLDivElement>(null);

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

    OzVault.create({
      pubKey:     process.env.NEXT_PUBLIC_VAULT_PUB_KEY!,
      sessionUrl: '/api/oz-session',
    })
      .then(v => {
        vault = v;
        v.createElement('cardNumber').mount(cardNumberRef.current!);
        v.createElement('expirationDate').mount(expiryRef.current!);
        v.createElement('cvv').mount(cvvRef.current!);
      })
      .catch(err => console.error('Vault init failed:', err));

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

  return (
    <>
      <div ref={cardNumberRef} />
      <div ref={expiryRef} />
      <div ref={cvvRef} />
    </>
  );
}
Or use the React provider — this is the recommended approach and handles lifecycle automatically:
// app/checkout/CardForm.tsx
'use client';

import { OzElements, OzCard, useOzElements } from '@ozura/elements/react';

function CheckoutForm() {
  const { createToken, ready } = useOzElements();
  return (
    <form onSubmit={async (e) => {
      e.preventDefault();
      const { token, cvcSession } = await createToken();
      // send token + cvcSession to your backend
    }}>
      <OzCard />
      <button type="submit" disabled={!ready}>Pay</button>
    </form>
  );
}

export function CardForm() {
  return (
    <OzElements
      pubKey={process.env.NEXT_PUBLIC_VAULT_PUB_KEY!}
      sessionUrl="/api/oz-session"
    >
      <CheckoutForm />
    </OzElements>
  );
}

Session route (app/api/oz-session/route.ts)

// app/api/oz-session/route.ts
import { Ozura, createSessionHandler } from '@ozura/elements/server';

const ozura = new Ozura({
  vaultKey: process.env.VAULT_API_KEY!,
});

export const POST = createSessionHandler(ozura);

Environment variables (.env.local)

NEXT_PUBLIC_VAULT_PUB_KEY=pk_live_...
VAULT_API_KEY=key_...
MERCHANT_API_KEY=ak_...
MERCHANT_ID=ozu_...
NEXT_PUBLIC_ prefix exposes the pub key to the browser bundle. All other credentials stay server-side only.

Production Checklist

Before you go live, confirm each of the following: Credentials
  • Production pub key registered to your domain (contact ammar@ozura.com)
  • VAULT_API_KEY set in server environment — never in browser code or committed to git
  • OzuraPay merchants: MERCHANT_API_KEY and MERCHANT_ID also set server-side
Session route
  • POST /api/oz-session (or your equivalent path) is deployed and reachable
  • Route uses createSessionHandler / createSessionMiddleware or manually calls ozura.createSession()
  • sessionLimit on the server matches sessionLimit in VaultOptions (both default to 3 — only change one if you change the other)
  • Session route is CSRF-safe (the SDK helpers enforce POST + Content-Type: application/json)
Security
  • CSP includes frame-src and connect-src for https://elements.ozura.com (see CSP above)
  • Charge route reads amount from your database, not from the request body
  • getClientIp used (or equivalent) when calling ozura.cardSale() so the Pay API gets the real client IP
Error handling
  • OzVault.create() is wrapped in try/catch with a fallback UI
  • Payment form disables the submit button until vault.isReady and all field 'ready' events fire
  • vault.reset() is called on tokenization error so the customer can re-enter card data
Testing
  • Tested end-to-end with the test pub key on localhost
  • Tested with the production pub key on your staging/preview domain before going live

Next Steps

Card Elements

Mount card number, expiry, and CVV fields.

Bank Elements

Mount account number and routing number fields.

React

OzElements provider and pre-built components.

Server SDK

Process payments and query transactions on your backend.