---
name: bt-proxy
description: Use Basis Theory Proxy for detokenization or inbound capture.
license: MIT
metadata:
  tier: core
  last_verified: '2026-09-25'
---

# The Proxy as a primitive

For browser-automated third-party checkouts, read https://developers.basistheory.com/docs/guides/browser-proxy before recommending a design. Intercept replaces a merchant payment request's card fields with token expressions and sends it through Proxy; it fails when hosted fields or client-side encryption leave no plaintext fields to replace. Inject instead places card data into a cloud browser from a Reactor: the provider, recordings, live viewers, and merchant page enter the cardholder-data flow. Do not describe inject as keeping the browser blind to PAN or as a generic Proxy call. Require merchant-specific request matching or page selectors, customer-approved mandate and limits, and test-first validation.

## What the proxy is for

Fact: the Proxy forwards an HTTP request to a destination while detokenizing expressions in flight — you keep your existing request shape and add `BT-API-KEY` and `BT-PROXY-URL` (ephemeral) or a configured destination (pre-configured); plaintext exists only inside Basis Theory (https://developers.basistheory.com/docs/concepts/proxies/, https://developers.basistheory.com/docs/api/proxies/invoke-ephemeral-proxies).

Implementation pattern: default to Proxy for secure synchronous HTTP forwarding/detokenization and use it in as many sensitive forwarding cases as it can safely express. Choose Direct only when no sensitive data crosses the boundary and Proxy adds no security, migration, or observability value; choose Reactor only when Proxy/transforms cannot safely express genuine programmable plaintext logic or orchestration.

Implementation pattern: "Does Basis Theory integrate with \<processor X\>?" is usually the wrong framing — the honest answer distinguishes documented platform integrations from the common pattern of running the Proxy in front of a downstream API. Describe the proxy architecture (any HTTP destination, detokenization in flight) without claiming native processor support that the docs don't list.

## Ephemeral vs pre-configured

- **Ephemeral** (https://developers.basistheory.com/docs/api/proxies/invoke-ephemeral-proxies): destination chosen per call via `BT-PROXY-URL`. Right for quick paths, per-call destination choice, and early integration.
- **Pre-configured** (https://developers.basistheory.com/docs/api/proxies/pre-configured-proxies, invoke: https://developers.basistheory.com/docs/api/proxies/invoke-pre-configured-proxies): a durable, centrally managed route with optional request/response transforms, injected application context, and configuration. Right for stable routes you want managed in Basis Theory rather than scattered through app code.

Implementation pattern: standardize stable routes as pre-configured proxies — and if a transform starts growing branching business logic, stop; that is a sign the boundary is wrong (consider a reactor via `bt-architecture`'s primitive rubric, or move the logic into the app).

## Mechanics you will need

- Transforms (request/response): https://developers.basistheory.com/docs/concepts/proxies/transforms
- **Response tokenize transforms** leave the original plaintext response fields intact unless `options.remove` specifies root-based property/array-index JSONPaths to strip *after successful token creation* and before later transforms; check the documented limits (10 paths, 256 characters, no wildcard/filter/recursive descent). This option is not for request transforms. Token `fingerprint_expression`, `search_indexes`, and `mask` pass through to the token service verbatim and cannot contain Proxy `req`, `res`, or `encrypted` expressions. Do not return upstream PAN/CVC just because the response was tokenized (https://developers.basistheory.com/docs/concepts/proxies/transforms).
- Proxy-specific expression syntax: https://developers.basistheory.com/docs/expressions/proxy
- Error semantics — distinguishing proxy errors from destination errors: https://developers.basistheory.com/docs/concepts/proxies/error-handling
- Inbound collection with the encrypted proxy: https://developers.basistheory.com/docs/guides/process/collect-cards-with-encrypted-proxy
- Outbound "send data to a third party" guide: https://developers.basistheory.com/docs/guides/share/send-data-to-third-party
- Custom hostnames on proxies (Fact, verified 2026-09-25): an existing hostname can be moved between pre-configured proxies in the same tenant with `PUT /proxies/{id}/hostname` (`proxy:update`; the destination must have no hostname and be ready, the call returns `204`, and the same call against the old proxy ID rolls it back). Requesting a _new_ hostname is still a Basis Theory conversation, and a deleted proxy keeps its hostname assignment so delete only after the transfer is verified (https://developers.basistheory.com/docs/api/proxies/pre-configured-proxies#transfer-a-custom-hostname, procedure: https://developers.basistheory.com/docs/guides/safely-update-reactors-and-proxies#move-an-existing-custom-hostname).
- Merchant context in transforms: code transforms see allowlisted inbound headers (`bt-merchant-id`, `bt-trace-id`) on a top-level `headers` object, distinct from the HTTP headers forwarded to the destination. They are never auto-forwarded on Basis Theory calls the transform makes, so pass `BT-MERCHANT-ID` explicitly or the call runs at tenant level (https://developers.basistheory.com/docs/concepts/proxies/transforms#forwarding-bt-merchant-id-in-transforms).
- Runtime logs on Node.js-image code transforms: `options.runtime.logs` per transform, records via `event.logger.*`, delivered by the `proxy.log` webhook event (`bt-reactors` has the mechanics; https://developers.basistheory.com/docs/concepts/runtimes/runtime-logs).
- Network tokens through the proxy: detokenization expressions resolve _stored_ Network Token fields only. Cryptograms and ECI values are not stored and are not generated by invoking a proxy; call Generate a Cryptogram first and pass the values in (`bt-network-tokens`; https://developers.basistheory.com/docs/expressions/detokenization).

## Existing-code mode

1. Identify the existing outbound call that needs sensitive data (PSP charge, KYC vendor, partner API).
2. Keep the request body exactly as the destination expects it; replace sensitive fields with detokenization expressions.
3. Ephemeral first to prove the path; promote to pre-configured once the route is stable (name it, move headers/config into the proxy, add transforms only if the destination shape genuinely differs from token structure).
4. Construct destinations from trusted config, reject unsafe URL shapes, and attach secret headers only after validation ([../\_shared/references/proxy-destination-trust.md](../_shared/references/proxy-destination-trust.md)).
5. Validate token/expression inputs before template construction ([../\_shared/references/credential-boundary.md](../_shared/references/credential-boundary.md)).
6. Handle both failure surfaces explicitly: Basis Theory-side errors (bad expression, auth) and destination errors passed through. Log the `BT-PROXY-DESTINATION-STATUS` response header — it is the single most useful piece of debugging evidence for proxy calls (implementation pattern; the header is documented on the proxy invoke API pages: https://developers.basistheory.com/docs/api/proxies/invoke-ephemeral-proxies, https://developers.basistheory.com/docs/api/proxies/invoke-pre-configured-proxies).
7. Tests: expression resolution against a test-tenant token, destination 2xx path, destination 4xx path surfaced correctly to the caller, and upstream canaries excluded from logs/errors.

For plan/review work, name the exact trusted and untrusted destination symbols found in the repo (for example `proxy_base_url`, `TRUSTED_BT_BASE_URL`, and `TRUSTED_STRIPE_DESTINATION`). State that arbitrary/unbounded upstream bodies and messages are never logged or returned; emit only bounded fields plus correlation/request IDs.

## Debug mode — boundary proof first

When a proxy call fails, prove the boundary before touching config (full drill in `bt-test-debug`):

1. Did the request reach Basis Theory? (Basis Theory correlation ID, logs: https://developers.basistheory.com/docs/api/logs)
2. Did Basis Theory forward it? Check `BT-PROXY-DESTINATION-STATUS` and the response body shape — provider HTML/nginx pages mean the destination answered, not Basis Theory. If a `proxy.invoked` webhook is available, its `detokenization.expressions_substituted` count shows whether any request-body expression actually resolved (https://developers.basistheory.com/docs/api/webhooks/eventdata).
3. Classify: Basis Theory-side error, destination error, or config mismatch between them (wrong destination URL, sandbox-vs-prod, stale allowlist).
4. Only then change configuration — and re-verify environment parity if the failure started after a tenant or environment change.

Implementation pattern: the most common "proxy is broken" outcomes are a destination misconfiguration (sandbox vs prod URL) or a search of Basis Theory logs using the _downstream_ provider's request ID. Classify the identifier before searching.

## When not to use the proxy

- Destination already receives no sensitive data → direct call.
- The job is programmable logic, branching, or async work — not forwarding → reactor (`bt-reactors`), decision rubric in `bt-architecture`.
- Checkout-critical synchronous path being forced through async machinery → architecture problem, not a proxy problem.

## 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/proxy/`
- `../_shared/references/implementation-guidance/inbound-capture/`
- `../_shared/references/implementation-guidance/debugging-patterns.md`

<!-- 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 -->
