Skip to main content
The hosted-fields origin is being provisioned. elements.knoxcall.com does not answer yet, so a field mounted against the defaults on this page will not load. Everything below describes shipped, tested code; it is documented ahead of the origin so the contract is stable when it goes live.
A hosted field is an <iframe> served by KnoxCall, on KnoxCall’s own origin, embedded in your checkout. The customer types into our document, not yours. When you call tokenize(), the value goes straight from that iframe to the KnoxCall API and your page receives a vault token — never the value, and never a ciphertext you could mishandle. What this is not: a management-API SDK. Neither package ever holds an API key. The only credential that reaches the browser is a single-use capability token your backend mints, bound to one vault and to your page’s exact origins, and it expires in minutes.

How it fits together

  1. Your backend mints a tokenize capability token (POST /v1/client-tokens), naming the vault and the origins your checkout is served from.
  2. Your page mounts <SecureField> with that token. The component builds one iframe pointed at https://elements.knoxcall.com/field?t=…&format=….
  3. KnoxCall resolves the token without spending it, and serves the field page with Content-Security-Policy: frame-ancestors <your bound origins>. Any other page that tries to frame it is refused by the browser.
  4. The customer types. The value lives in our document only.
  5. You call tokenize(). The iframe posts the value once, to POST /v1/client/tokenize, authenticated with the capability token. The token is spent atomically.
  6. Your page gets onToken: a vault token, an id, an expiry, and — for card fields only — display-safe metadata.
  7. Your backend stores the token and later sends it through POST /v1/proxy with {{ token: … }}, which swaps the real value back in on the way to your processor.
The value exists in exactly two places it did not before: our iframe, and our vault. It is never in your page’s JavaScript, your DOM, your logs or your database.

Install

@knoxcall/react needs react >= 18 and takes @knoxcall/browser as a peer dependency. Neither package has any other runtime dependency. On npm: @knoxcall/browser and @knoxcall/react. Source for both packages: github.com/KnoxCall/sdk-browser.

1 · Mint the capability token (backend)

This is the only step that uses your API credential, and it must happen on your server.
Hand data.token to the page. That is all the browser ever receives.

The origin binding is exact

origins entries must be exactly an origin — scheme, host, optional port, and nothing else. These are all refused at the mint, with a message naming the problem: A checkout that genuinely runs on several origins lists each one, or mints per page — minting is a backend call you already make for every capture.

2 · Mount the field

React

useSecureField() gives you the state a checkout actually needs: tokenize() to call on submit, complete to enable the button, the last error, and token once it arrives. Pass your own onChange/onError/onToken into the hook to compose — they run after the hook’s state update, never instead of it. Every prop:
If you override elementBase, you must override origins to match. Otherwise every message from your own iframe is discarded by the origin check and the field looks like it never loads. That is the safe failure and it is deliberate: a default that widened itself to whatever elementBase said would trust a base somebody else set.

Vanilla

mountSecureField returns a handle: tokenize(), setStyle(style), on(event, handler) (returns an unsubscribe function), destroy(), and the iframe element for layout. Call destroy() when you unmount — it removes the iframe and the message listener.

Formats

format asks for which inputs are drawn, and nothing else. It reaches no decision about what the field tells your page, and none about where the value lands: the vault’s real format is enforced server-side against the vault the capability was bound to, so a wrong or missing format cannot put a value somewhere it should not go. It is a request, not an instruction. The field draws the layout you asked for only if the bound vault admits it, and otherwise draws the vault’s own. ready tells you which one it drew. So a capability bound to an ssn vault draws the SSN input whatever the URL says, and the card form is only ever drawn for a card vault — see Card fields. A missing or unrecognised format falls back to the first layout the vault admits.

Events

token.token is the vault token to store. token.id is the vault-token row id, for metadata updates and audit correlation. expires_at is an ISO timestamp, or null when the vault sets no TTL. last4, brand, exp_month and exp_year are emitted only when the capability is bound to a card (pan) vault — the vault’s real format, read from the capability’s own binding, never the format in the URL. An SSN’s last four digits are what a call centre asks for to prove identity, so they are not display-safe merely because a card’s are; format="pan" over a non-card vault gets none of them, and does not draw the card form either.

Error codes

A closed set, so you can branch on code rather than string-matching a message written for a human.

Styling

The iframe is cross-origin, so your CSS cannot reach inside it — by design. You theme it with a constrained style object instead, either as the fieldStyle prop or field.setStyle(...). Unknown keys and invalid values are ignored, not applied. fontFamily is a name because a font-family value can carry a local() or url() source, which is a fingerprinting and injection surface for no design benefit a named stack does not already give. Layout — width, height, margin — belongs on the iframe element, via style or className.

The message protocol

You do not need this to use the packages. It is here because the iframe is a security boundary and you may want to audit it, or write your own plumbing. Parent → iframe Iframe → parent Each side parses only the direction it may receive, so a page that echoes our own knox:token back at us gets nowhere, and the iframe will not obey a message shaped like one of its own outputs. If you write your own plumbing, all three of these checks are required, in this order:
  1. event.origin is one of the KnoxCall element origins — an exact string match, never a prefix. https://elements.knoxcall.com.evil.com starts with everything we own and ends with nothing we own.
  2. event.source is your iframe’s contentWindow. An origin check does not cover a different window at the same origin.
  3. The payload validates against the shapes above.
Shape validation alone is not sufficient, and neither is the origin check alone. mountSecureField and <SecureField> do all three.

What the iframe will not do

  • It never accepts the capability token from a message. It reads it from its own URL. A parent that could supply the token could supply one bound to a vault in another tenant.
  • It never accepts its vault, its vault’s format, its API base or its trusted origins from a message. The server injects all four, and the format is what every display-safe field above is gated on — so nothing your page can set changes what the field is willing to tell it.
  • It never posts the value to the parent, writes it to storage, puts it in a URL, or logs it. The value leaves the document once, in the body of one POST /v1/client/tokenize.
  • It clears and disables its inputs the instant a token comes back.
The iframe carries sandbox="allow-scripts allow-same-origin". allow-same-origin looks alarming and is required: without it the document gets an opaque origin, so it cannot read its own injected configuration and its request would carry Origin: null — which the server refuses, because null is the origin every sandboxed document on the internet shares. The frame is cross-origin to your page, so allow-same-origin restores only its own origin and grants it nothing of yours. allow-forms, allow-popups, allow-top-navigation and allow-modals are all deliberately withheld.

Card fields

format="pan" draws and validates a card number, expiry and security code — and only over a pan vault. A pan vault refuses browser tokenization: the mint answers 403, and a field pointed at one reports knox:error with code card_program_unavailable. Those two rules meet: because the card form is drawn for a card vault only, and a card vault cannot mint a browser capability, no card number can be typed into a KnoxCall-served field today at all. format="pan" over any other vault draws that vault’s own layout. KnoxCall does not currently hold a PCI DSS Attestation of Compliance, so browser capture of card numbers is switched off and nothing on this page reduces anyone’s cardholder-data scope. The refusal is not a configuration flag you can turn off; it is checked at the mint and again when the token is spent. Card capture opens when the attestation exists, and not before. Every other format on this page is available today and needs no attestation of any kind.

Test cards

Test mode has card numbers that fail on purpose, so a decline path and a 3-D Secure path can be exercised before the first real card: They are honoured in the Test data space only, and the full contract — the exact response bodies, the X-Knox-Error header, and what a simulated decline still records — is in Vaults → Test cards.
These work on the SERVER-SIDE path today, and not through a hosted field. A pan vault refuses browser tokenization at the mint (see above), so a card number — test or real — cannot be typed into a KnoxCall-served field at all until the attestation exists. Tokenize the test card from your backend (POST /v1/vaults/{vault}/tokens) and spend the token through POST /v1/proxy or a Route. The table is not a second implementation waiting to be written: the same module answers both paths, so hosted fields inherit it on the day card capture opens.

Local development

Run the API process with NODE_ENV=test for a local round trip. The field page renders under NODE_ENV=development, but /v1 chooses its data space (Live or Test) from the Host header, and the loopback→Test mapping exists only on a test-mode server — so under npm run dev the tokenize POST gets a 404 and the field reports network_error. With NODE_ENV=test everything below completes, in the Test data space, against Test-typed keys and vaults. That is exactly how KnoxCall’s own browser end-to-end suite runs the flow.
The default elementBase is https://elements.knoxcall.com. Against a local stack, point both settings at your own server and bind the capability to your page’s loopback origin:
Plain http origins are accepted only for loopback hosts and only by a server running in development or test mode; a deployed KnoxCall refuses them, including http://localhost. The same is true of the elements.localhost spelling the elementBase above uses.

Never put the capability token in your page’s URL

Mint the kct_ on your backend and hand it to the page in the response body — rendered into the HTML, or returned from a fetch your page makes. Do not put it in the checkout URL, in either the query string or the fragment. A URL that carries a live capability rides in the Referer header of every subresource that page loads, including third-party scripts, fonts, images and analytics beacons. It also lands in your own access logs, your CDN’s logs, and the browser’s history. The capability is short-lived and single-use, but it is live for the minutes that matter, and it is bound to your origins — which a script already running on your page is served from. This is about your page, not ours: the field’s own document sends no Referer on any request, including the one that carries the value (asserted from a real browser in KnoxCall’s end-to-end suite), and its URL is the one place a kct_ legitimately appears.