<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
- Your backend mints a
tokenizecapability token (POST /v1/client-tokens), naming the vault and the origins your checkout is served from. - Your page mounts
<SecureField>with that token. The component builds one iframe pointed athttps://elements.knoxcall.com/field?t=…&format=…. - 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. - The customer types. The value lives in our document only.
- You call
tokenize(). The iframe posts the value once, toPOST /v1/client/tokenize, authenticated with the capability token. The token is spent atomically. - Your page gets
onToken: a vault token, an id, an expiry, and — for card fields only — display-safe metadata. - Your backend stores the token and later sends it through
POST /v1/proxywith{{ token: … }}, which swaps the real value back in on the way to your processor.
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.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:
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 oncode 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 thefieldStyle
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:
event.originis one of the KnoxCall element origins — an exact string match, never a prefix.https://elements.knoxcall.com.evil.comstarts with everything we own and ends with nothing we own.event.sourceis your iframe’scontentWindow. An origin check does not cover a different window at the same origin.- The payload validates against the shapes above.
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.
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.
Local development
The defaultelementBase 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:
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 thekct_ 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.