> ## 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 an iOS app

> Present the Vault's pages in a Safari view inside your iOS app, or show the wallet natively with the AgentcardWalletKit Swift package.

Your iOS app can use the Vault in two ways, and they combine. Present the links in a Safari view: the add-a-card page and the approval page open inside your app, and the user's passkey works there. Or add AgentcardWalletKit, a SwiftUI package that shows the user their cards as a native sheet.

## Open the Vault in a Safari view

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

```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 '{}'
```

```swift theme={null}
import SafariServices
import SwiftUI

struct VaultView: UIViewControllerRepresentable {
    let url: URL   // the vault session url, or an approval link

    func makeUIViewController(context: Context) -> SFSafariViewController {
        SFSafariViewController(url: url)
    }

    func updateUIViewController(_ controller: SFSafariViewController, context: Context) {}
}

// Present it like any sheet:
.sheet(isPresented: $showVault) {
    VaultView(url: vaultSessionURL)
}
```

Use `SFSafariViewController` rather than `WKWebView`. The Vault saves the user's card behind a passkey, and a Safari view shares Safari's passkeys, so a returning user unlocks with Face ID inside your app. Inside a `WKWebView` the page cannot complete the passkey prompt and asks the user to open the link in Safari instead.

Open the approval link the same way. When your agent reaches the payment form, `onApprovalUrl` fires with a link; present it in the same Safari view, 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 natively

AgentcardWalletKit is a SwiftUI package. You pass it a wallet link and present it like any other sheet; the wallet screens are native and talk to the API directly. The package is in early access: the repository is private, so [ask us](mailto:karen@agentcard.sh) for an invite and we add your GitHub account.

### 1. Install the package

In Xcode, add the package and pin it at `0.1.0` or later:

```text theme={null}
https://github.com/tiny-agent-company/agentcard-wallet-ios
```

### 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 hand the app only the `url`. Your `client_secret` never ships in the app binary. A link lasts 15 minutes by default; pass `expires_in` (60 to 86400 seconds) to change that.

### 4. Present the sheet

```swift theme={null}
import AgentcardWalletKit

.sheet(isPresented: $showWallet) {
    AgentcardWalletSheet(link: walletLinkURL) { event in
        switch event {
        case .cardAttached(let cards):
            // the user added a card; cards carries id, last4 and brand
        case .tokenExpired:
            // create a fresh wallet link on your server and present the sheet again
        default:
            break
        }
    }
}
```

`AgentcardWalletSheet` also accepts `linkToken:` if you would rather pass the raw token than the URL. The sheet handles its own failures and tells the user what to do, so the one case your code owns is `.tokenExpired`: the link or its session ended, so create a fresh link and present the sheet again.

## Confirm with webhooks

The sheet's events tell your app what the user just did. Your server should act on webhooks, because an event can fire on a sheet the user then dismisses:

* `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. 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).
