---
name: bt-collect
description: Collect sensitive data into Basis Theory tokens or token intents.
license: MIT
metadata:
  tier: core
  last_verified: '2026-09-25'
---

# Collect cards and sensitive data

## The decision that matters first: token or token intent?

Fact: token intents are short-lived resources for collecting and validating data from public applications before deciding on long-term retention — default TTL 1 day, conversion to a durable token is an explicit step, and card CVC does not survive conversion (https://developers.basistheory.com/docs/concepts/token-intents, https://developers.basistheory.com/docs/api/tokens/token-intents).

Implementation pattern — position token intents as a _commitment delay mechanism_:

- **Token intent first** when: the user is not sure they need durable storage yet, validation/3DS/first-charge should happen before retention, or a migration wants "collect and send now, redesign storage later." 3DS sessions accept a `token_intent_id` directly (Fact: https://developers.basistheory.com/docs/api/3ds/sessions), so auth-first flows work without storing anything.
- **Token directly** when: durable reuse is already a known requirement (subscriptions, cards on file) and no validation gate is needed before storage.
- Never describe token intents as "temporary tokens" or durable storage — the recurring failure mode is teams surprised by TTL and the CVC-drop on conversion. If they need the CVC at charge time, the charge must happen from the intent (or recollect CVC — see `bt-payments`).
- Call the resource a **token intent** in the answer. That is its product name and the phrase the customer will search the docs and API reference for; `token_intent_id` is the field name, and "token-intent" hyphenated only works as an adjective in front of a noun ("token-intent collection"). An answer that never spells out the noun leaves the reader without the term they need to look anything up.

## Choosing the collection surface

| Situation                                                     | Recommendation                                              | Canonical docs                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ------------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Web checkout, standard                                        | Web Elements current/default; migrate legacy versions first | https://developers.basistheory.com/docs/sdks/web/web-elements/v3/getting-started; if existing code uses `@basis-theory/basis-theory-js` or pinned Web Elements v1/v2 APIs, advise migration via https://developers.basistheory.com/docs/sdks/web/web-elements/v3/migration before copying current examples.                                                                                                                                                                                      |
| React                                                         | React flavor of Web Elements                                | https://developers.basistheory.com/docs/guides/collect/collect-data-with-react                                                                                                                                                                                                                                                                                                                                                                                                                   |
| Single input (e.g. just CVC or just PAN)                      | Single element pattern                                      | https://developers.basistheory.com/docs/guides/collect/collect-cards-single-element                                                                                                                                                                                                                                                                                                                                                                                                              |
| Existing styled inputs the team won't give up                 | "Use your own inputs" pattern                               | https://developers.basistheory.com/docs/guides/collect/collect-cards-with-your-inputs, https://developers.basistheory.com/docs/card-payments/use-your-own-inputs                                                                                                                                                                                                                                                                                                                                 |
| iOS / Android / React Native                                  | Mobile Elements SDKs                                        | https://developers.basistheory.com/docs/guides/collect/collect-data-with-ios, https://developers.basistheory.com/docs/guides/collect/collect-data-with-android, https://developers.basistheory.com/docs/guides/collect/collect-data-with-react-native-on-ios; React Native card inputs now document `autoComplete` / `textContentType` autofill, `enterKeyHint`, `onSubmitEditing`, and `iconPosition` brand icons (https://developers.basistheory.com/docs/sdks/mobile/react-native/components) |
| A browser agent fills the card for the shopper (experimental) | Web Elements v3 `card` element with `agentTools: true`      | https://developers.basistheory.com/docs/card-payments/agent-checkout and https://developers.basistheory.com/docs/sdks/web/web-elements/v3/agent-tools — see "Agent tools" below; this is a merchant checkout surface, not a docs or tenant tool                                                                                                                                                                                                                                                  |
| Card data arriving inbound via your API (partners, files)     | Inbound collection                                          | https://developers.basistheory.com/docs/guides/collect/collect-inbound-sensitive-data, https://developers.basistheory.com/docs/card-payments/receive-cards-api                                                                                                                                                                                                                                                                                                                                   |
| Bank accounts                                                 | Bank collection guide                                       | https://developers.basistheory.com/docs/guides/banks/collect-bank-accounts, https://developers.basistheory.com/docs/api/enrichments/bank-accounts                                                                                                                                                                                                                                                                                                                                                |

Fact: Elements keep raw values out of the customer's JavaScript and servers — the element captures input inside Basis Theory-controlled contexts, which is what reduces PCI scope (https://developers.basistheory.com/docs/concepts/elements). If the team insists on their own inputs, say plainly that raw values then transit their frontend code and the scope story changes; the use-your-own-inputs docs cover when that trade is acceptable.

Form styling and UX questions: https://developers.basistheory.com/docs/guides/collect/customize-web-form. Token shapes and card types: https://developers.basistheory.com/docs/concepts/what-are-tokens, https://developers.basistheory.com/docs/api/tokens/token-types, https://developers.basistheory.com/docs/api/tokens/tokenize.

## When the customer owns their forms and won't adopt iframes

Implementation pattern: platforms, billing providers, and PCI-certified teams with existing card UIs frequently reject iframe collection outright. Do not push hosted Elements when the customer needs to preserve its own inputs, and do not fall back to raw-PAN "use your own inputs" by default — there is a third surface built for exactly this shape.

Fact (verified 2026-09-25): Basis Theory supports client-side encrypted collection with the customer's own form. Create a Client Encryption Key from the backend (`POST /keys` — https://developers.basistheory.com/docs/api/client-keys; `public_key_pem` is returned only at creation, so capture it then), pass its `public_key_pem` and `key_id` to the frontend, encrypt the card payload in the browser/app into a JWE with the current Web Elements `tokens.encrypt` service (ECDH-ES + A256GCM, so RSA keys are not supported — https://developers.basistheory.com/docs/sdks/web/web-elements/v3/services#encrypting-data), and pass the opaque payload through the customer's backend in the `encrypted` field on token or token intent creation (https://developers.basistheory.com/docs/api/tokens/, https://developers.basistheory.com/docs/api/tokens/token-intents). If the project is pinned to legacy Web Elements v2 or `@basis-theory/basis-theory-js`, advise migration before changing imports or service signatures. No plaintext PAN transits the customer's servers, and the frontend keeps its existing inputs.

Implementation-pattern benefits that repeat: no frontend rewrite, tokenization can run async or batched (decoupled from checkout latency), and a resilience story — the encrypted payload can be queued and tokenized when the path is available, instead of failing the capture.

Decision rule (implementation pattern):

- Customer **owns their forms** and wants to keep them → encrypted-collection pattern above.
- Customer has **no forms**, wants turnkey, or white-labels checkout → Elements.
- Card data arrives **from partners** in a fixed request shape → inbound collection (proxy), not client encryption.

The raw-values warning stands: "use your own inputs" _without_ client-side encryption means raw values transit their frontend code and the PCI-scope story changes.

## Web Elements v3 options that change answers (verified 2026-09-25)

Read the current components page before citing option names (https://developers.basistheory.com/docs/sdks/web/web-elements/v3/components). Recent additions that customers ask about:

- **Dual writing with the HTTP client** (`bt.client.get/post/put/patch/delete`): sends element data from inside the iframe straight to a third party, with no Basis Theory credentials and without passing through Basis Theory. Both conditions must hold or the call rejects: `allowHttpClient: true` at initialization (frozen after init) and a destination on the Elements allowlist, which is empty by default and extended through Basis Theory support; a blocked or unreachable host rejects with `status: -1`, which means no response, not that nothing was sent. Responses come back unsanitized, so a processor echoing a full PAN reaches the page and the PCI impact must be confirmed with the customer's assessor. It is not a fallback for Basis Theory being unavailable (https://developers.basistheory.com/docs/sdks/web/web-elements/v3/services#http-client).
- **BIN enrichment**: `binLookup` on the card / card number element adds an asynchronous `binDetails` (`brand`, `type`, `country`, `bank`, `segment`) to `change` events, scoped to the first six digits; prefer the local `cardBrand` for responsive UI and treat `binDetails` as issuer data that may be absent (https://developers.basistheory.com/docs/sdks/web/web-elements/v3/components#bin-enrichment). Co-badge additional brands land on the token's `card.additional` array (`bt-tokens`).
- **CVV `conceal`** (default `true`, runtime-changeable) and `showToggle` (creation-only) control CVV visibility; a visible CVV is readable on screen even though the value never leaves the iframe (https://developers.basistheory.com/docs/sdks/web/web-elements/v3/components#conceal).
- **`enterKeyHint`** maps to the HTML attribute on any input element (per sub-field on `card`); invalid values throw `ConfigurationError` (https://developers.basistheory.com/docs/sdks/web/web-elements/v3/components#enterkeyhint).
- **Right-to-left**: `direction: 'rtl'` and `language` are initialization options; card number, expiration, and CVV stay left-to-right by design, Arabic-Indic digits are normalized, and `direction` is fixed for the element's life (https://developers.basistheory.com/docs/sdks/web/web-elements/v3/right-to-left).
- **`cardDisplay`** is a read-only element that shows a stored card from a token ID through a session the page never sees (`sessionAuthorizationUrl`, backend authorizes with a Private key, `reveal` rule scoped to one token). It is a display surface, so route it to `bt-issuing` for issued cards or `bt-pii` for stored third-party cards (https://developers.basistheory.com/docs/sdks/web/web-elements/v3/components#carddisplay).
- **Migration notes**: dual writing and the `binLookup` element option have shipped in v3; the standalone `bt.binLookup()` method and custom card brands are not being ported, and other APIs remain unavailable or pending. Check the migration page's “Not yet available in V3,” “Coming to V3,” and “Not being ported” sections before scoping a migration (https://developers.basistheory.com/docs/sdks/web/web-elements/v3/migration).

## Agent tools on the card element (experimental)

Fact (https://developers.basistheory.com/docs/sdks/web/web-elements/v3/agent-tools, https://developers.basistheory.com/docs/card-payments/agent-checkout): creating the `card` element with `agentTools: true` registers one WebMCP tool, `enter_card`, inside the Basis Theory iframe so a browser agent can fill the card for the shopper. Card data the agent sends goes from the browser to Basis Theory without passing through the merchant page, and the tool returns only `complete`, `brand`, `last4`, and `errors` — never the values. Merchant-side scope: the page registers its own checkout tools (order summary, `submit_payment`) with `document.modelContext.registerTool`, offers `submit_payment` only while the card is complete, and returns the actual processor charge outcome rather than a token or payment-method reference. Share one in-flight attempt between the tool and Pay button, keep tool registration through its result, and use server-side order idempotency for ambiguous retries. The merchant guide covers both an existing Elements checkout and a hidden agent-only card beside another processor's form. Check the current components docs before recommending `cvcRequired` or other options.

How to answer these asks:

- Say plainly that WebMCP is an experimental browser API (Chrome flag or origin trial); in other browsers `agentTools` does nothing, so the normal form must keep working without it.
- Keep the credential boundary: page scripts pass element references to `tokens.create`, `tokens.encrypt`, or the HTTP client and never read plaintext from the element or the tool result. Do not write code that reads, logs, or forwards the PAN or CVC an agent supplied. The agent and its platform own how they handle the card number they typed.
- Mounting an agents-only card beside another processor's form must use `hidden` or `display: none`; other hiding techniques leave inputs focusable. Paying through the processor from the tool uses the HTTP client rules above.
- Do not conflate this with `bt-agentic` (Agentic Payments credentials), with any docs-site tooling, or with tenant actions: the Basis Theory docs site does not expose an MCP server or a WebMCP skills tool, and public agent tools must never be wired to authenticated tenant operations.

## Existing-code mode

1. Locate the current card/PII entry point and the Elements package/API version in use: `@basis-theory/web-elements`, `@basis-theory/react-elements`, or deprecated `@basis-theory/basis-theory-js`.
2. State the assumption set: framework, public/secret key availability, whether a backend endpoint exists to receive the token.
3. For new/current work, wire Web Elements v3/default or React Elements with a **public application key** in the frontend; tokens/intents are created client-side and only the token ID crosses to the backend. If the codebase uses deprecated `@basis-theory/basis-theory-js` or pinned Web Elements v1/v2 APIs, do not mix API shapes; advise migration via https://developers.basistheory.com/docs/sdks/web/web-elements/v3/migration first. Never move raw values through the customer's backend "temporarily." Name the credential as a public application key in the answer, not just as `BT_PUBLIC_KEY` in a config block — which key type goes in the browser is the single most consequential thing the reader has to get right here, and a variable name does not teach it.
4. Keep their component structure and styling approach; Elements are added inside their form, not as a page replacement.
5. Load `bt-tokens` for token types, schemas, masks, fingerprints, search, and typed references.
6. Apply [../\_shared/references/credential-boundary.md](../_shared/references/credential-boundary.md) before persistence: reject PAN-like values, CVC-like values, control characters, and expression-injection strings in token-shaped fields.
7. Add tests: form renders, tokenization is invoked on submit, backend receives a token/intent ID, and backend token fields reject raw card-shaped input (mock the Basis Theory client in unit tests; use a test tenant for integration).
8. Final report, in the reply itself, under **Done** / **Assumed** / **Remaining**: which flows now tokenize, what you assumed about their key types and framework, and what stays custom (their backend storage model, their PSP call — route to `bt-payments`). A changed-files list plus "tests pass" is not this report: it tells the reader what you touched, not what is still unbuilt, and reads as a finished integration. If a sandbox limit stopped you from proving a leg — no test tenant key, no live Elements run — that belongs under **Remaining**, named as unproven.

## POC scaffold mode

Follow `bt-start/references/poc-conventions.md`. Minimal shape: a static page or small app with current Web Elements v3 collecting a test card into a token intent, a tiny backend endpoint that receives the intent ID and echoes the masked result, `.env.example` with `BT_PUBLIC_KEY=<BT_PUBLIC_KEY>`, and a smoke test against a test tenant. Keep any working reference implementation customer-owned or generated in the target repository so the proof path matches their framework and credentials boundary.

## Gotchas that repeat in common integrations

- Implementation pattern: collecting with a **secret** key in the browser is the most common first mistake — public/client keys only in frontends (Fact on key types: https://developers.basistheory.com/docs/api/authentication, https://developers.basistheory.com/docs/api/client-keys).
- Implementation pattern: if existing code uses older Web Elements routes or deprecated `@basis-theory/basis-theory-js`, preserve only for maintenance and recommend migration to Web Elements v3 before expanding the integration.
- Implementation pattern: teams treat token intents as storage and hit TTL expiry in QA a day later. Decide convert-vs-expire explicitly at design time.
- Implementation pattern: storage-first advice slows adoption when the customer only needs collect-and-send today. Staged adoption (intent → charge → convert if reuse emerges) preserves optionality.
- Sandbox: all collection work runs against a test tenant first; test tenants are not for live cardholder data (https://developers.basistheory.com/docs/api/test-tenants).

## Basis Theory implementation guidance

When the answer needs implementation judgment beyond public docs, load only the relevant slices below from [../\_shared/references/implementation-guidance/README.md](../_shared/references/implementation-guidance/README.md). Treat these as implementation patterns, not canonical product behavior; keep public docs as the source of truth and label the guidance as **Implementation pattern** or **Assumption** unless docs prove it.

- `../_shared/references/implementation-guidance/elements/`
- `../_shared/references/implementation-guidance/token-intents/`
- `../_shared/references/implementation-guidance/inbound-capture/`

<!-- bt-conventions:start -->

## Working conventions (shared by all Basis Theory skills)

- Public Basis Theory docs are the source of truth for product behavior. Link the specific docs page behind any product claim; if a skill and the docs disagree, follow the docs.
- Keep customer ownership clear: customers own PSP accounts, credentials, compliance decisions, production rollout, and application code. Skills can guide plans, bounded changes, and examples; they do not certify production readiness.
- Never request, echo, store, or commit real credentials. Use placeholders such as `<BT_API_KEY>` and prefer sandbox or synthetic data for examples.
- If a live-looking credential, live cardholder data, or production secret appears, stop mutation work, tell the user exactly where it appeared, and require rotation/revocation before continuing.
- Default to test tenants and PSP sandboxes. Do not run or recommend production charges, refunds, card reveals, credential migrations, or data moves unless the user explicitly asks and the plan names safeguards, rollback, and customer approval.
- Treat vague planning, audit, review, and “should we” prompts as read-only unless the user explicitly asks for a bounded implementation change.
- Before changing product code, map the existing checkout/data flow, credential-bearing paths, persistence, retries, refunds, webhooks, logs, and rollback expectations.
- End with an honest final report: done, assumed, remaining, proof run, unverified live behavior, rollback, and go-live items.
<!-- bt-conventions:end -->
