> ## Documentation Index
> Fetch the complete documentation index at: https://docs.knoxcall.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Browser & React Elements

> @knoxcall/browser + @knoxcall/react — seal PANs and PII in the page, reveal with single-use capability tokens. No API key ever ships to the browser.

Two client-side packages shrink your PCI/PII compliance scope the way Stripe Elements does:

| Package             | What it does                                                                                                                                                |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@knoxcall/browser` | Pure client-side ECIES sealing (P-256 → HKDF-SHA256 → AES-256-GCM), single-use reveal via `KnoxClient`, and the framework-agnostic Elements iframe protocol |
| `@knoxcall/react`   | `<CardCollect>` and `<Reveal>` — iframe-isolated React components built on the browser package                                                              |

The idea: a sensitive value (a PAN, an SSN, an API credential) is sealed into a portable `kc:` ciphertext **in the page**, using your tenant's public key — plaintext never reaches your servers. The `kc:` string is byte-compatible with KnoxCall's server-side format, so only KnoxCall (holding the private key) can decrypt it. These are client-side packages, not management-API SDKs — they never hold an API key.

## Install

<Note>
  Not yet published to npm — install from a monorepo checkout by path. `@knoxcall/browser` requires WebCrypto (any modern browser, or Node ≥ 18 for SSR/tests); `@knoxcall/react` needs `react >= 18` and `@knoxcall/browser` as a peer.
</Note>

```sh theme={"dark"}
npm install /path/to/KnoxCall/sdk/knoxcall-browser /path/to/KnoxCall/sdk/knoxcall-react
```

## Seal in the browser

**1. Server side** — fetch the sealing bundle with your API key (the bundle contains only *public* material, so it is safe to hand to the page):

```typescript theme={"dark"}
// Your backend (Node management SDK), e.g. GET /api/sealing-bundle
import { KnoxCall } from '@knoxcall/sdk';

const knox = new KnoxCall({ apiKey: process.env.KNOXCALL_API_KEY });
app.get('/api/sealing-bundle', async (_req, res) => {
  res.json(await knox.crypto.getSealingBundle()); // GET /v1/encrypt/sealing-bundle
});
```

**2. Browser side** — seal the value before it ever leaves the page:

```typescript theme={"dark"}
import { createEncryptor } from '@knoxcall/browser';

const bundle = await fetch('/api/sealing-bundle').then((r) => r.json());
const encryptor = createEncryptor(bundle, { purpose: 'pci' });

const ciphertext = await encryptor.encrypt('4242424242424242');
// -> "kc:1:s:<key_ref>:<eph_pubkey>:<iv>:<ct||tag>:$"

// Send the ciphertext to your backend and store it as-is. Your servers only
// ever see the kc: string; decryption happens via POST /v1/decrypt with your
// API key (or never, if you proxy it onward with the Ephemeral Proxy).
```

`encrypt()` accepts strings, finite numbers, booleans, `null`, and JSON-serializable objects/arrays; the original type is preserved through decryption. See [Browser-Side Encryption](/essentials/encryption/browser) for the underlying format.

## Capture cards with `<CardCollect>`

`<CardCollect>` embeds a KnoxCall-hosted **cross-origin iframe** that captures card details and seals the PAN *inside* the iframe. Your page — and any XSS on it — only ever receives the ciphertext (plus display-safe `last4` / `brand`). That collapses your frontend PCI scope toward SAQ A.

```tsx theme={"dark"}
import { useEffect, useState } from 'react';
import { CardCollect } from '@knoxcall/react';
import type { SealingBundle } from '@knoxcall/browser';

function CheckoutForm() {
  const [bundle, setBundle] = useState<SealingBundle | null>(null);
  const [complete, setComplete] = useState(false);

  useEffect(() => {
    fetch('/api/sealing-bundle').then((r) => r.json()).then(setBundle);
  }, []);

  if (!bundle) return null;
  return (
    <CardCollect
      bundle={bundle}
      purpose="pci"
      onChange={setComplete}
      onToken={(ciphertext, { last4, brand }) => {
        // ciphertext is a kc: string — safe to send to and store on your
        // backend as-is. The PAN itself never left the KnoxCall iframe.
        fetch('/api/cards', { method: 'POST', body: JSON.stringify({ ciphertext, last4, brand }) });
      }}
      onError={(msg) => console.error('card element error:', msg)}
    />
  );
}
```

## Reveal with capability tokens

Reading a value back in the browser never uses an API key. Your backend mints a **single-use, payload-pinned capability token** (`kct_…`) bound to the exact ciphertext or vault token, and hands only that token to the page:

```typescript theme={"dark"}
// Your backend: mint a capability token bound to one ciphertext.
app.post('/api/reveal-token', async (req, res) => {
  // POST /v1/client-tokens -> { token: "kct_…", expires_at, action }
  res.json(await knox.crypto.mintClientToken({ action: 'decrypt', data: storedCiphertext }));
});
```

Consume it browser-side — either headless with `KnoxClient`:

```typescript theme={"dark"}
import { KnoxClient } from '@knoxcall/browser';

const { token } = await fetch('/api/reveal-token', { method: 'POST' }).then((r) => r.json());
const knox = new KnoxClient(); // optionally { baseUrl, fetchImpl }

const pan = await knox.reveal(token, storedCiphertext);        // POST /v1/client/decrypt
const raw = await knox.detokenize(token, 'tok_visa_abc123');   // POST /v1/client/detokenize
```

…or with the `<Reveal>` component, which renders the plaintext to the *user* inside the iframe — your JavaScript never sees it:

```tsx theme={"dark"}
import { Reveal } from '@knoxcall/react';

<Reveal
  capabilityToken={token}        // kct_… from your backend, single-use
  ciphertext={storedCiphertext}  // the kc: value bound into that token
  action="decrypt"               // or "detokenize" for vault tokens
  onError={(msg) => setError(msg)}
/>
```

The token is consumed server-side on first use; replaying it fails. `KnoxClient` refuses anything that is not a `kct_…` token before making a network call, so an API key pasted into the browser by mistake never leaves the page.

## Security model

* **No API key in the browser.** Sealing needs only the public bundle; revealing needs only a short-lived `kct_` capability token minted by *your* backend. Nothing shipped to the page can decrypt at will.
* **Single-use, payload-pinned reveal tokens.** A `kct_` token is bound to one specific ciphertext/vault token and is consumed on first use.
* **Context-bound ciphertexts.** The HKDF `info` pins tenant, app key, key version, datatype, and optional `purpose` (data-role, e.g. `"pci"`) into the derived key; the `kc:` header rides as AES-GCM AAD. Decrypting under the wrong purpose, key, or a tampered header simply fails.
* **Exact-origin iframe trust.** Element messages are only accepted from an allowlisted origin (exact string match — no prefix or substring semantics, so `https://api.knoxcall.com.evil.com` never passes), from the expected iframe window, and only after strict shape validation. Outbound messages are posted with an explicit `targetOrigin`, never `*`.

<Warning>
  If you override `elementBase` (the origin the iframe is served from), you must also pass `origins` — the default allowlist covers production and sandbox only, so messages from any other origin are silently dropped.
</Warning>

## Full reference

Component props, the raw `postMessage` protocol (`parseKnoxMessage`, `isTrustedElementOrigin`, `KNOX_MESSAGE`), and development setup: [`sdk/knoxcall-browser/README.md`](https://github.com/knoxcall/knoxcall/tree/main/sdk/knoxcall-browser) and [`sdk/knoxcall-react/README.md`](https://github.com/knoxcall/knoxcall/tree/main/sdk/knoxcall-react) in the monorepo.
