@zhexio/zhex-js
v0.1.0
Published
Zhex Elements — hosted card fields for PCI-safe tokenization. Mounts an iframe served by js.zhex.io and exchanges a Zhex token your server can charge.
Maintainers
Readme
@zhexio/zhex-js
Hosted card fields for Zhex — the Stripe Elements equivalent. Embeds an iframe served by js.zhex.com that collects the PAN/CVC and exchanges them for a single-use Zhex token your server can then charge.
Status: alpha — public API stable enough to integrate against; the iframe app is in active development. Track docs.zhex.com/concepts/tokenization.
Why
Your server should never see a raw card number. With @zhexio/zhex-js the PAN/CVC stay inside an iframe under our domain — your page only sees a token like tok_…. That keeps your PCI scope at SAQ-A (the smallest possible) and removes a class of breaches you can't afford to handle.
Install
npm install @zhexio/zhex-js
# or
pnpm add @zhexio/zhex-jsQuick start
import { Zhex } from '@zhexio/zhex-js';
const zhex = Zhex('zk_live_xxx'); // your publishable key
const elements = zhex.elements(); // optional theme/locale
const card = elements.create('card');
card.mount('#card-element'); // any container in your DOM
card.on('change', ({ complete, error }) => {
document.querySelector('button')!.toggleAttribute('disabled', !complete);
if (error) showInlineError(error.message);
});
document.querySelector('button')!.addEventListener('click', async () => {
const { token, error } = await zhex.createToken(card, {
holderName: 'Jane Cardholder',
});
if (error) return showInlineError(error.message);
// POST `token!.id` to your server, then call /v1/payment_intents.
});Security model
- PAN/CVC never touch your domain. They are typed into an iframe served by
js.zhex.com. Your site only ever receives the token. - Origin allowlist. Each merchant registers their site origin(s) in the Zhex dashboard (or via
POST /v1/merchant_origins). The Zhex API rejects token creation from origins not in the allowlist — even if azk_*key leaks. - Single-use, 15-minute TTL. Tokens can be exchanged for a charge exactly once; after expiry or first use, they 404.
- Sandboxed iframe. Strict
sandboxandallowattributes; CSPframe-ancestorscontrolled per-merchant; noallow-same-origin.
API
Zhex(publishableKey, options?)
| Option | Type | Default | Notes |
|---|---|---|---|
| iframeBaseUrl | string | https://js.zhex.com | Override for dev/e2e. |
| apiBaseUrl | string | https://api.zhex.com | Override for dev/e2e. |
Returns a ZhexInstance with elements() and createToken().
elements.create('card')
Creates a CardElement. Call .mount(target) with a CSS selector or DOM node.
card.on(event, handler)
| Event | Payload |
|---|---|
| 'ready' | void — iframe finished loading. |
| 'change' | { complete, empty, brand, error } |
| 'focus' / 'blur' | field: string |
Returns an unsubscribe function.
card.update({ appearance?, locale? })
Re-themes the iframe at runtime.
card.destroy()
Removes the iframe and listeners. Safe to call multiple times.
zhex.createToken(card, { holderName? })
Resolves with { token: TokenSummary } on success or { error: ElementError } on failure. The token shape mirrors the Zhex API:
type TokenSummary = {
id: string; // tok_…
livemode: boolean;
card: {
brand: CardBrand | null;
last4: string | null;
expMonth: number | null;
expYear: number | null;
holderName: string | null;
};
expiresAt: string; // ISO 8601
createdAt: string;
};Roadmap
- Apple Pay / Google Pay request buttons (same iframe-hosted pattern)
- 3DS / authentication flows surfaced via the same Element
- Split fields (
cardNumber,cardExpiry,cardCvc) for granular layouts - Pix and boleto Elements
License
MIT — see LICENSE.
