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

# Add the Vault to a React Native app

> Open the Vault's pages in the system browser from your React Native app, or render the wallet with the @agent-cards/wallet-react-native component.

Your React Native app can use the Vault in two ways, and they combine. Open the links in the system browser: the add-a-card page and the approval page open in Safari or Chrome, where the user's passkey works. Or add the `@agent-cards/wallet-react-native` component, which renders the wallet in your app and shows the user their cards.

## Open the Vault from your app

Create a vault session on your server, hand the `url` to the app, and open it with `Linking`.

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v2/vault_sessions \
  -H "Authorization: Bearer $ORG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```tsx theme={null}
import { Button, Linking } from "react-native";

async function addCard() {
  const session = await fetch("https://your-server.example.com/agentcard/vault-session", { method: "POST" })
    .then((r) => r.json());
  await Linking.openURL(session.url);
}

<Button title="Add a card" onPress={addCard} />
```

Open the link in the system browser, not in a `WebView` you control. The Vault saves the user's card behind a passkey, and the system browser is where that passkey lives, so a returning user unlocks with Face ID or fingerprint. Inside a `WebView` the page asks the user to open the link in Safari or Chrome instead. On iOS, `expo-web-browser` or a Safari view gives the same result without leaving your app.

Open the approval link the same way. When your agent reaches the payment form, `onApprovalUrl` fires with a link; open it with `Linking`, or send it by push notification if the user is not in the app.

When the user finishes, you receive `vault.session_linked` with their `user_id`, then `vault.card_stored`. Store the `user_id`: it is what you pass as `user` on every checkout. Without webhooks, [poll the session](/vault/adding-a-card#option-b-poll-the-session) instead.

## Show the wallet in your app

The component renders the wallet inside your app. The user sees the cards in their vault, and "Add a card" opens the Vault in the system browser. Card numbers never enter your code. The component needs a wallet link, and a wallet link needs a user who has connected to your platform, so the setup has four steps.

### 1. Install the component

```bash theme={null}
npm install @agent-cards/wallet-react-native react-native-webview
```

### 2. Connect the user

A wallet link is issued to a connected user. Send the user a one-time code, verify it, and record their consent. The `user.id` you get back is the same id the Vault uses.

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v2/connect/start \
  -H "Authorization: Bearer $ORG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"phone": "+15551234567"}'
```

Then [verify the code](/api-reference/connections/verify) and [record consent](/api-reference/connections/consent) for the returned `user.id`. In test mode the code is always `111111`.

### 3. Create a wallet link

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v2/wallet_links \
  -H "Authorization: Bearer $ORG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "usr_8f3k2m"}'
```

```json theme={null}
{
  "object": "wallet_link",
  "id": "wl_7h2k9m",
  "user_id": "usr_8f3k2m",
  "status": "active",
  "url": "https://app.agentcard.sh/w/wl_7h2k9m.4f8s…",
  "expires_at": "2026-09-15T18:15:00Z",
  "test_mode": false
}
```

Create the link on your server and give the app only the `url`. Your `client_secret` never ships in the app bundle. A link lasts 15 minutes by default; pass `expires_in` (60 to 86400 seconds) to change that.

### 4. Render the wallet

```tsx theme={null}
import { AgentcardWallet } from "@agent-cards/wallet-react-native";

<AgentcardWallet
  link={walletLinkUrl}
  onSuccess={(data) => {
    // data.type is "card_attached" (with data.cards) or "payment_completed"
  }}
  onTokenExpired={() => {
    // fetch a fresh wallet link from your server and swap the `link` prop
  }}
/>
```

The component accepts `link` as either the full URL or the raw token. When the user taps "Add a card", the component opens the Vault in the system browser; pass `onOpenUrl(url)` to take over how that link opens. `onEvent(name, data)` receives every event for analytics, `theme="light"` matches a light app, and `style` sizes the view.

When the link or its session ends, `onTokenExpired` fires. Fetch a fresh link from your server and swap the `link` prop; do not reuse the old one.

## Confirm with webhooks

`onSuccess` tells your app what the user just did. Your server should act on webhooks, because the event can fire on a screen the user then leaves:

* `vault.card_stored` when a card lands in the vault.
* `checkout_authorization.approved` and the other `checkout_authorization.*` events when purchases are approved or declined.

## Test it

A sandbox token creates a sandbox session, a sandbox connection and a sandbox wallet link. The connect code is always `111111`. Store any of [Stripe's published test cards](https://docs.stripe.com/testing), any future expiry, any CVC. In test mode, "Add a card" inside the component stores an instant test card and fires `card_attached` without opening the Vault; a live session opens the Vault. Rehearse a purchase against [shop.agentcard.sh](https://shop.agentcard.sh), a demo store on Stripe test mode.

Next: [Create a cart](/vault/creating-a-cart).
