Skip to main content

What Secure Elements is

Secure Elements is a JavaScript SDK, @fluz/secure-elements, that mounts an isolated, Fluz-hosted frame directly into a container element on your page. The frame renders or collects sensitive data; your page and your servers only ever hold a short-lived, opaque token that authorizes one specific action. It covers three capabilities: For reveal specifically, this is a third way to show a user their own card details, alongside the two you already have: The same tradeoff applies to collecting a card: a widget lets Fluz capture the PAN inside its own PCI scope, and Secure Card Input gets you the same “never touches your servers” property inline in your own page instead of a modal. VAN Reveal follows the same pattern as Card Reveal, but for a virtual account number’s account and routing number instead of a card’s PAN, expiry, and CVV.
If you’re already using the Embedded Widget for everything, you don’t need this. Secure Elements is for headless or API-driven integrations that still need to show or collect card details without opening the full widget modal or pursuing PCI_COMPLIANCE.

How it works

Your backend mints a client token

Exchange your existing Fluz OAuth access token for a short-lived client token, scoped to a single reveal or a single card-add.

Your frontend mounts the frame

Hand the client token to @fluz/secure-elements, which mounts the Fluz-hosted frame into a container you provide — inline in your page, not a modal.

The SDK reports back via callbacks

Your page never reads the raw card data. It only sees success, error, or mount events.

Prerequisites

  • Your application is registered with Fluz. The scope required on your access token depends on which capability you’re minting a client token for:
  • For a reveal token specifically, you also need an ACTIVE virtual card id, owned by the account you’re revealing, to pass when minting it.
  • For a van-reveal token specifically, you also need a virtualAccountNumberId, owned by the account you’re revealing, to pass when minting it — the same id getSpendAccountVirtualAccountNumbers returns, and the same LIST_PAYMENT scope that call already requires.
Ask Fluz to allow-list your origin before you write any code. The frame refuses to render from an origin Fluz hasn’t pre-approved — there’s no self-serve toggle for this today, so it’s the one prerequisite that can block you if you leave it for later.Email partnerships@fluz.app (or your account manager, if you have one) with your application name or ID and every origin you need approved — each http://localhost:PORT you develop against, plus your staging and production domains. Fluz allow-lists them on the backend; there’s nothing to configure on your side once that’s done.

Environments

frameHostOrigin is optional on createCardViewer — omit it and it defaults to production (https://secure.fluz.app). Pass it explicitly to target staging. Only these two exact origins are accepted; anything else throws a FluzElementsError (error.code === "INVALID_FRAME_HOST_ORIGIN") as soon as you call createCardViewer, before any frame is mounted.

Loading the SDK

@fluz/secure-elements isn’t published to npm — load it as a browser global (IIFE) build from Fluz’s CDN with a <script> tag. It exposes a FluzSecureElements global:
Every code sample on this page and on Card Reveal assumes you’ve loaded the script tag and destructured what you need off FluzSecureElements, as above. Each release publishes to an immutable, version-pinned path (.../v0.1.0/index.global.js) and a floating .../latest/index.global.js that always points at the newest release. Pin to a specific version for anything beyond a prototype — latest can change under you without notice.

Mint a client token

Your backend calls this using the Fluz OAuth access token you already obtain through the standard OAuth grant flow. Never send that access token to the browser — only the clientToken / loadToken pair this endpoint returns should reach your frontend.
purpose is "reveal" (with a virtualCardId), "tokenization" (no virtualCardId — see Secure Card Input), or "van-reveal" (with a virtualAccountNumberId instead of a virtualCardId — see VAN Reveal).
A successful mint returns 201. Both tokens are single-purpose and short-lived — mint a new pair every time. clientToken is what authorizes the reveal or card-add itself (expiresIn seconds — 300 by default for reveal, 1800 for tokenization); loadToken is scoped even tighter (60 seconds) since it travels in a URL and is rejected everywhere except loading the frame. Pass both straight into createCardViewer or renderFieldsForTokenization, and never put clientToken in a URL yourself — the SDK already keeps it out of one.
| Status | Error | Meaning | | :— | :— | :— | | 400 | invalid_purpose | purpose missing or invalid | | 400 | virtual_card_id_required | missing virtualCardId for a reveal token | | 400 | virtual_account_number_id_required | missing virtualAccountNumberId for a van-reveal token | | 401 | unauthorized | missing access token | | 401 | invalid_token | access token failed verification (malformed, expired, wrong signature) | | 403 | insufficient_scope | access token missing the scope required for this purpose — see Prerequisites | | 403 | app_not_registered | your application isn’t registered — contact Fluz | | 403 | forbidden | card or virtual account number not found, or not owned by this user | | 429 | rate_limited | too many client-token requests for this user — see the Retry-After header | | 500 | internal_error | unexpected server error |

Styling fields

createCardViewer, createAccountViewer, and renderFieldsForTokenization all accept an optional style object, applied to every field they mount:
style is validated before anything is sent to the frame. If a value doesn’t match what’s documented below, await viewer.mount(...) rejects with a FluzElementsError (error.code === "INVALID_STYLE") — wrap your mount() call in a try/catch if you’re accepting configurable style input yourself. width, height, border, and backgroundColor style the box around each field — but which element that is differs by capability, since reveal and Secure Card Input mount different DOM shapes:
  • createCardViewer / createAccountViewer (reveal) — each field is its own frame holding a single value, rendered as one inline-block element, so these four properties apply directly to it.
  • renderFieldsForTokenization (Secure Card Input) — the fields share one frame, each inside its own wrapper box. These properties apply to that wrapper, not the input text itself. See Box-model styling and Layout on the Secure Card Input page for the layout option this also unlocks.
fontFamily must be an exact, case-sensitive match for one of two allowlists:
  • System fonts — common OS/web-safe stacks (system-ui, -apple-system, Helvetica Neue, Arial, Georgia, Menlo, and the generic monospace / serif / sans-serif keywords, among others). These render immediately, with no network request.
  • Google Fonts — any family from the Google Fonts catalog ("Roboto", "Inter", "IBM Plex Mono", and so on), passed exactly as Google lists it. The SDK loads the font for you — you don’t need a <link> tag or @font-face rule.
A Google Font is fetched after the field mounts, not bundled up front, so there’s a brief window on a cold cache where the field renders in the browser’s fallback font before swapping in your chosen one. A system font has no such delay.
Both lists are exported if you want to validate a font choice, or build a font picker, yourself:

Content Security Policy

If your page sets a CSP, allow the frame host you’re targeting, and the CDN host the SDK loads from:
The SDK itself loads from script-src’s allowed host; the inline <script> block in your own integration code (as in the examples on this page and on Card Reveal, Secure Card Input, and VAN Reveal) still needs its own nonce or hash if your CSP doesn’t allow 'unsafe-inline'. If you use a nonce, the value on the tag must match the one in the header exactly:

Security model

  • Your OAuth access token never leaves your servers.
  • The client token your frontend holds is opaque and single-purpose — it carries no card data and can’t be replayed for a different card or action.
  • Card data is only ever readable inside Fluz-hosted frames, isolated from your page’s own JavaScript. Every frame is sandboxed (allow-scripts allow-same-origin allow-forms) with referrerPolicy="no-referrer" — the SDK never puts card data in the DOM outside of them.
  • The frame only renders inside origins you’ve pre-registered with Fluz.
  • Card data never reaches your servers or your page’s JavaScript — this is the condition most relevant to SAQ A eligibility under PCI DSS, alongside the other requirements SAQ A sets (e.g. not otherwise storing, processing, or transmitting cardholder data yourself). Your applicable PCI DSS obligations depend on your full cardholder data environment; confirm your scope with your QSA.

Next steps

Card Reveal

Create the card viewer, mount it, and control which fields are revealed.

Secure Card Input

Collect a physical card and add it as a payment method.

VAN Reveal

Show a user the account and routing number of their own virtual account number.

Live demos

See all three capabilities running against staging.

Example integrations

Runnable plain-HTML and React examples, both calling real staging infrastructure.

OAuth applications

How to obtain the access token you’ll exchange for a client token.