React Agentic
react-agentic
The Basis Theory @basis-theory/react-agentic package provides React components and hooks for building agentic commerce flows. It handles enrollment verification (OTP + passkey creation) and instruction approval (passkey authentication) with pre-built UI components.
React Agentic covers the Agents, Enrollments, and Instructions integration. For the Agentic Payments flow built on payment methods and allowances, use the Web Agentic SDK, which works in React without a wrapper.
Visa's verification SDK requires your application to be served over HTTPS, even in development, and Visa verification will fail on plain HTTP. Mastercard verification uses a backend-driven redirect with no client-side SDK, so it continues to work on localhost without HTTPS.
For local development, we recommend Cloudflare Quick Tunnels to expose your local server over HTTPS with zero configuration:
# Install cloudflared
brew install cloudflared
# Start a quick tunnel pointing to your local dev server
cloudflared tunnel --url http://localhost:3000
This gives you a temporary https://*.trycloudflare.com URL you can use for testing.
Installation
- npm
- yarn
npm install @basis-theory/react-agentic
yarn add @basis-theory/react-agentic
The package declares peer dependencies on react and react-dom with the supported range ^18.2.0 || ^19.0.0.
Setup
Wrap your application with the BtAiProvider to authenticate requests using your Public API key:
import { BtAiProvider } from '@basis-theory/react-agentic';
function App() {
return (
<BtAiProvider apiKey="<PUBLIC_API_KEY>">
<YourApp />
</BtAiProvider>
);
}
BtAiProvider Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey | string | Yes | - | Basis Theory public API key |
environment | "production" | "test" | "sandbox" | No | "production" | Environment to use. test uses a mock Visa SDK; sandbox targets Visa's sandbox environment. Both use the test API. |
agenticApiUrl | string | No | - | Override the agentic commerce API base URL |
passkeyTimeoutMs | number | No | 60000 | Timeout in milliseconds for Visa passkey authentication. Increase if users need more time to complete passkey prompts. |
sessionReadyTimeoutMs | number | No | 10000 | Timeout in milliseconds for Visa session readiness after refresh. |
onNewDevice | "decline" | "reenroll" | No | "reenroll" | Behavior when instruction verification detects the consumer is on a different device than the one used during enrollment. "decline" shows an error asking the user to return to the original device. "reenroll" prompts the user to re-enroll the card on the current device. |
theme | object | No | Dark theme | Partial theme overrides for the built-in verification modals. Deep-merged with the default dark theme — only override what you need. See Theming. |
In the test environment, the SDK uses a mock Visa SDK and validates real SDK configurations in the background, logging warnings if production setup has issues. This lets you develop locally without HTTPS while still catching configuration problems early.
Hooks
useAgentic
The useAgentic hook provides methods for enrollment verification, instruction approval, and SDK status. It must be used within a BtAiProvider.
import { useAgentic } from '@basis-theory/react-agentic';
function MyComponent() {
const {
ready,
verifyEnrollment,
verifyInstruction,
getStatus,
updateApiKey,
} = useAgentic();
// ...
}
Return Values
| Property | Type | Description |
|---|---|---|
ready | boolean | true when the SDK is initialized and ready for verification calls. Mastercard needs no client-side SDK, so readiness follows the Visa SDK. On localhost, where Visa cannot load, ready is still reported for Mastercard-only use. |
verifyEnrollment | (enrollmentId: string) => Promise | Opens a modal for enrollment verification. Safe to call before ready — it waits automatically. |
verifyInstruction | (agentId: string, instructionId: string) => Promise | Opens a modal for instruction verification. Safe to call before ready — it waits automatically. |
getStatus | () => { visa: boolean, mastercard: boolean } | Returns per-network readiness. mastercard is always true because Mastercard requires no client-side SDK. |
updateApiKey | (newApiKey: string) => void | Dynamically changes the API key, triggering re-initialization. |
apiKey | string | The current API key. |
environment | string | The current environment. |
verifyEnrollment
Renders a modal that handles the enrollment provider's verification flow: Visa OTP and passkey creation, or Mastercard hosted authentication.
const result = await verifyEnrollment(enrollmentId);
| Parameter | Type | Description |
|---|---|---|
enrollmentId | string | The enrollment ID to verify |
See the Agents, Enrollments, and Instructions guide for a complete usage example.
If verification fails and the user closes the modal, the promise rejects with an error that includes error details (traceId, debug, code, status) when available:
try {
const result = await verifyEnrollment(enrollmentId);
} catch (error) {
console.error(error.message); // Human-readable error message
console.error(error.traceId); // BT-TRACE-ID for support
console.error(error.code); // Error code (e.g., "PROVIDER_VERIFICATION_FAILED")
}
verifyInstruction
Renders a modal that approves a spending instruction through Visa passkey authentication or Mastercard hosted authentication.
const result = await verifyInstruction(agentId, instructionId);
| Parameter | Type | Description |
|---|---|---|
agentId | string | The agent ID |
instructionId | string | The instruction ID to verify |
See the Agents, Enrollments, and Instructions guide for a complete usage example.
If verification fails and the user closes the modal, the rejected error includes the same error details as verifyEnrollment.
useEnrollmentVerification
Action-based hook for building a custom enrollment verification UI. Render your UI based on status and call the corresponding action to advance the flow. Use this instead of verifyEnrollment() when you need full control over the verification experience.
The hook handles orchestration for both card networks:
- Visa: OTP method selection → OTP input → passkey creation → completion
- Mastercard: popup-based authentication → completion
import { useEnrollmentVerification } from '@basis-theory/react-agentic';
function CustomEnrollmentUI({ enrollmentId }) {
const {
status,
error,
otpMethods,
start,
selectOtpMethod,
submitOtp,
confirmPasskey,
retry,
cancel,
} = useEnrollmentVerification({ enrollmentId });
// Render your own UI based on status
}
Input Props
| Prop | Type | Required | Description |
|---|---|---|---|
enrollmentId | string | Yes | The enrollment ID to verify |
Statuses
| Status | Description | Available Actions |
|---|---|---|
idle | Not started | start() |
loading | Async operation in progress | — |
otp_required | Show OTP method picker (otpMethods available) | selectOtpMethod(methodId) |
otp_sent | Show OTP code input | submitOtp(code) |
passkey_required | Visa passkey creation needed | confirmPasskey() |
redirect_required | Mastercard's hosted flow needs a popup | confirmPasskey(), retry() |
verified | Verification complete | — |
error | Something went wrong (error has message) | retry(), start() |
Return Values
| Property | Type | Description |
|---|---|---|
status | string | Current status (see table above) |
error | string | null | Error message when status is error |
errorDetails | object | null | Structured error info when status is error. See error details object. |
otpMethods | array | Available OTP delivery methods when status is otp_required |
start | () => Promise | Begin or restart the verification flow |
selectOtpMethod | (methodId: string) => Promise | Select an OTP delivery method |
submitOtp | (code: string) => Promise | Submit the OTP code |
confirmPasskey | () => Promise | Trigger passkey creation (Visa) or popup authentication (Mastercard) |
retry | () => Promise | Retry the current step |
cancel | () => void | Cancel and reset to idle |
How to Build Your Own UI
- Call
start()to kick off the enrollment verification flow. - Switch on
statusto render the appropriate UI for each step:loading— show a spinner or loading indicatorotp_required— render a list or buttons fromotpMethodsand callselectOtpMethod(methodId)on selectionotp_sent— render a code input field and callsubmitOtp(code)on submitpasskey_required— show a prompt explaining passkey creation and callconfirmPasskey()redirect_required— show a prompt explaining Mastercard's popup authentication and callconfirmPasskey()verified— show a success message and proceed with your app flowerror— display theerrormessage with a button toretry()orstart()again
- Use
cancel()to let the user abort and reset toidleat any point.
The hook manages all card-network orchestration internally — you only need to render UI and call actions.
useInstructionVerification
Action-based hook for building a custom instruction verification UI. Render your UI based on status and call the corresponding action to advance the flow. Use this instead of verifyInstruction() when you need full control over the verification experience.
The hook handles authentication for both card networks:
- Visa: passkey authentication → completion
- Mastercard: popup-based authentication → completion
import { useInstructionVerification } from '@basis-theory/react-agentic';
function CustomInstructionUI({ agentId, instructionId }) {
const {
status,
error,
start,
confirmPasskey,
retry,
cancel,
} = useInstructionVerification({ agentId, instructionId });
// Render your own UI based on status
}
Input Props
| Prop | Type | Required | Description |
|---|---|---|---|
agentId | string | Yes | The agent ID |
instructionId | string | Yes | The instruction ID to verify |
onNewDevice | "decline" | "reenroll" | No | Behavior when a cross-device passkey mismatch is detected. Defaults to the value set on BtAiProvider. |
Statuses
| Status | Description | Available Actions |
|---|---|---|
idle | Not started | start() |
loading | Async operation in progress | — |
passkey_required | Visa passkey auth needed | confirmPasskey() |
redirect_required | Mastercard's hosted flow needs a popup | confirmPasskey(), retry() |
verified | Verification complete | — |
error | Something went wrong (error has message) | retry(), start() |
When a cross-device passkey mismatch is detected, the hook enters the error status with a message determined by onNewDevice: "reenroll" asks the user to re-enroll the card on the current device, and "decline" asks them to return to the original device.
Return Values
| Property | Type | Description |
|---|---|---|
status | string | Current status (see table above) |
error | string | null | Error message when status is error |
errorDetails | object | null | Structured error info when status is error. See error details object. |
start | () => Promise | Begin or restart the verification flow |
confirmPasskey | () => Promise | Trigger passkey auth (Visa) or popup authentication (Mastercard) |
retry | () => Promise | Retry the current step |
cancel | () => void | Cancel and reset to idle |
How to Build Your Own UI
- Call
start()to kick off the instruction verification flow. - Switch on
statusto render the appropriate UI for each step:loading— show a spinner or loading indicatorpasskey_required— show a prompt explaining Visa passkey authentication and callconfirmPasskey()redirect_required— show a prompt explaining Mastercard's popup authentication and callconfirmPasskey()verified— show a success message and proceed with your app flowerror— display theerrormessage with a button toretry()orstart()again. CheckerrorDetailsfor trace IDs when reporting issues. Cross-device passkey mismatches surface here with theonNewDeviceguidance message.
- Use
cancel()to let the user abort and reset toidleat any point.
The hook manages all card-network orchestration internally — you only need to render UI and call actions.
Error Details Object
When a verification hook enters the error status due to an API error, errorDetails provides structured information for debugging. It is null for non-API errors.
| Property | Type | Description |
|---|---|---|
traceId | string | null | The BT-TRACE-ID response header from the failed API call. Include this when contacting support. |
debug | object | null | Additional debug information from the API error response (e.g., provider correlation IDs). |
code | string | null | The error code from the API (e.g., PROVIDER_VERIFICATION_FAILED). See error codes. |
status | number | null | The HTTP status code of the failed response. |
errorDetails is cleared automatically when a new action is called (e.g., start(), retry(), cancel()).
Theming
The built-in verification modals use a design token system that you can customize via the theme prop on BtAiProvider. Pass a partial theme object — unset tokens fall back to the default dark theme.
import { BtAiProvider } from '@basis-theory/react-agentic';
function App() {
return (
<BtAiProvider
apiKey="<PUBLIC_API_KEY>"
theme={{
colors: {
primary: '#1a1a1a',
text: {
primary: '#1a1a1a',
secondary: '#6b7280',
},
background: {
overlay: 'rgba(0, 0, 0, 0.5)',
surface: '#ffffff',
input: '#f9fafb',
button: {
primary: '#1a1a1a',
primaryText: '#ffffff',
},
},
border: {
default: '#d1d5db',
focus: '#1a1a1a',
},
},
}}
>
<YourApp />
</BtAiProvider>
);
}
You can also import defaultTheme to inspect the full token structure:
import { defaultTheme } from '@basis-theory/react-agentic';
console.log(defaultTheme);
Theme Tokens
The theme object supports the following token categories. All tokens are optional — only override what you need.
colors
| Token | Default | Description |
|---|---|---|
primary | #f4f4f5 | Primary accent color |
error | #ef4444 | Error state color |
success | #10b981 | Success state color |
text.primary | #ffffff | Primary text color |
text.secondary | #a1a1aa | Secondary/muted text color |
text.placeholder | #71717a | Input placeholder text color |
text.error | #fca5a5 | Error message text color |
background.overlay | rgba(0,0,0,0.85) | Modal backdrop overlay |
background.surface | rgba(13,13,15,1) | Modal surface background |
background.input | #18181b | Input field background |
background.card | rgba(23,23,26,1) | Card/panel background |
background.button.primary | #f4f4f5 | Primary button background |
background.button.primaryText | #000000 | Primary button text color |
background.button.secondary | rgba(255,255,255,0.1) | Secondary button background |
background.button.secondaryText | #ffffff | Secondary button text color |
background.button.disabled | #52525b | Disabled button background |
border.default | #52525b | Default border color |
border.focus | #f4f4f5 | Focused input border color |
border.modal | rgba(255,255,255,0.1) | Modal border color |
border.error | rgba(239,68,68,0.3) | Error state border color |
spinner.track | rgba(244,244,245,0.3) | Loading spinner track color |
spinner.indicator | rgba(244,244,245,1) | Loading spinner indicator color |
typography
| Token | Default | Description |
|---|---|---|
fontFamily | Inter, -apple-system, ... | Primary font stack |
fontSize.title | 24px | Modal title font size |
fontSize.body | 14px | Body text font size |
fontSize.small | 12px | Small text font size |
fontSize.input | 18px | OTP input font size |
fontWeight.normal | 400 | Normal font weight |
fontWeight.medium | 500 | Medium font weight |
fontWeight.semibold | 600 | Semibold font weight |
borders
| Token | Default | Description |
|---|---|---|
radius.modal | 8px | Modal border radius |
radius.input | 8px | Input field border radius |
radius.button | 8px | Button border radius |
radius.option | 8px | OTP method option border radius |
spacing
| Token | Default | Description |
|---|---|---|
modal | 32px | Modal internal padding |
section | 16px | Section spacing |
input | 8px | Input element spacing |
buttonGap | 8px | Gap between buttons |