> ## 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, where the user's passkey works.

Your iOS app presents the Vault's 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.

## 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.

Create the session on your server and hand the app only the `url`. Your `client_secret` never ships in the app binary.

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.

## Confirm with webhooks

The user dismissing the Safari view is a claim. Your server should act on webhooks, because the view can close before your app hears anything:

* `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. Present the link on your own device, 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).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.