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

# Wallet SDK

> Drop the Agentcard wallet into your product with one server call and a script tag. Users attach cards and approve agent payments without ever leaving your page.

Show your users their Agentcard wallet inside your own product. Your backend
mints a wallet link, your page loads one script, and the wallet renders in an
overlay (or inline): card attachment, the card list, and the pay sheet all run
on Agentcard's surface inside an iframe. Card numbers never touch your page or
your servers, so your PCI scope stays exactly where it is: nowhere.

The same wallet link works two ways. Text it to the user and it opens the
hosted wallet page (an App Clip on iOS). Drop it into the SDK and the wallet
opens inside your product. One mint, both surfaces.

<Note>
  The Wallet SDK is rolling out with design partners. It needs two things on
  your organization: wallet links enabled, and your page's origin registered
  for embedding. [Contact us](mailto:support@agentcard.sh) and we will switch
  both on.
</Note>

## The shape

1. **Your server** mints a wallet link for a connected user:
   `POST /api/v2/wallet_links` with your client-credentials token.
2. **Your page** passes the link to `AgentCard.create()` and calls `open()`.
3. **The user** attaches a card or approves a payment inside the sheet.
4. **Your server** hears the results on
   [webhooks](/companies/webhooks): `connection.created`,
   `connected_card.updated`, `card.*`, `wallet_link.opened`. Treat SDK events
   as UI signals and webhooks as the truth.

## Quick start

Load the SDK from Agentcard on every page that uses it. Do not bundle or
self-host the file: it is versioned at `/js/v1/` and updates itself, which is
how fixes reach your users without you shipping anything.

```html theme={null}
<script src="https://app.agentcard.sh/js/v1/agentcard.js"></script>
```

Mint the link server-side, hand its URL to the page, and open the wallet:

```js theme={null}
// Server (any language): mint with your org client-credentials token.
// POST https://api.agentcard.sh/api/v2/wallet_links
// { "user_id": "<connected user id>", "expires_in": 900 }
// -> 201 { id, url, status: "active", expires_at, test_mode }

// Browser:
const wallet = AgentCard.create({
  token: link.url, // the wallet link URL (or its raw token)
  onSuccess: (data) => {
    // { type: "card_attached", cards: [...] }
    // { type: "payment_completed", merchant, amount_cents }
  },
  onExit: () => {},
  onEvent: (name, data) => analytics.capture(name, data),
  onTokenExpired: async () => {
    const fresh = await fetch("/api/agentcard/wallet-link").then((r) => r.json());
    wallet.update({ token: fresh.url });
  },
});

wallet.open(); // overlay sheet
// or: wallet.mount("#wallet")  — inline, always visible
```

That is the whole integration. The link is safe to expose to the browser: it
is a short-lived capability scoped to that one user's wallet, not an API
credential, and it expires (15 minutes by default, configurable with
`expires_in`).

## Opening the pay sheet

When your agent needs the user to approve a specific payment, open the wallet
with a pay intent. The user picks a card and confirms the amount in one step:

```js theme={null}
const wallet = AgentCard.create({
  token: link.url,
  pay: { merchant: "DoorDash", amountCents: 2350 },
  onSuccess: (data) => {
    if (data.type === "payment_completed") markOrderPaid();
  },
});
wallet.open();
```

Some banks ask for an extra approval step. The SDK opens the bank's page in a
new tab (it cannot render inside a frame; banks forbid that) and the sheet
waits with a check button until the approval lands. If the browser blocks the
automatic tab, the sheet shows a "Continue to your bank" button instead, so
the user is never stuck.

## `AgentCard.create(config)`

| Option           | Type     | What it does                                                            |
| ---------------- | -------- | ----------------------------------------------------------------------- |
| `token`          | string   | The wallet link URL or its raw token. Required for the live wallet.     |
| `pay`            | object   | `{ merchant, amountCents }`. Opens the pay sheet instead of the wallet. |
| `onSuccess`      | function | A card was attached or a payment completed. Receives `{ type, ... }`.   |
| `onExit`         | function | The user closed the sheet.                                              |
| `onEvent`        | function | `(name, data)` stream of everything below, for analytics.               |
| `onTokenExpired` | function | The link died. Mint a fresh one and call `update({ token })`.           |
| `onOpenUrl`      | function | Override how bank-approval URLs open. Default: a new tab.               |
| `zIndex`         | number   | Overlay stacking. Default `2147483000`.                                 |

The returned handler: `open()` shows the overlay, `mount(el)` renders inline
in your element, `update(config)` swaps the token or pay intent, `exit()`
closes the overlay, `destroy()` removes everything and stops listening.

## Events

Every event also arrives on `onEvent(name, data)`:

| Event           | When                                                      |
| --------------- | --------------------------------------------------------- |
| `ready`         | The sheet booted and is safe to show.                     |
| `resize`        | `{ height }` for sizing inline mounts.                    |
| `event`         | Step stream (`step_changed`, `link_unavailable`, ...).    |
| `success`       | `{ type: "card_attached" \| "payment_completed", ... }`.  |
| `token_expired` | The link or session ended. Mint a fresh link, `update()`. |
| `open_url`      | `{ url }` the host should open top-level (bank approval). |

## iOS, fully native

For iMessage-adjacent and native apps, `AgentcardWalletKit` renders the same
wallet with native SwiftUI — no iframe, no loaders. Attach, the wallet, and
the pay sheet are native screens calling the API directly; the only webview
inside is the hosted card capture and bank ceremony, which keeps your PCI
scope at zero. Available to design partners as a Swift package.

```swift theme={null}
.sheet(isPresented: $showWallet) {
    AgentcardWalletSheet(
        link: link.url,
        pay: AgentcardPayIntent(
            merchant: "DoorDash",
            amountCents: 2350,
            reference: order.id  // your stable id for this payment
        ),
        onEvent: { event in
            if case .paymentCompleted = event { markOrderPaid() }
        }
    )
}
```

`reference` is your own id for the payment (an order or checkout id). It is
what keeps two different payments with the same amount separate: retries of
one payment reuse its reference, distinct payments never share one.

Events mirror the web SDK exactly: `ready`, `stepChanged`, `cardAttached`,
`paymentCompleted`, `tokenExpired`.

## React Native

`@agentcard/wallet-react-native` wraps the same sheet for React Native apps
(requires `react-native-webview`). Same wallet link in, same events out; bank
approvals open in the system browser.

```tsx theme={null}
<AgentcardWallet
  link={link.url}
  pay={{ merchant: "DoorDash", amountCents: 2350 }}
  onSuccess={(d) => d.type === "payment_completed" && markOrderPaid()}
  onTokenExpired={() => refreshLink()}
/>
```

## Testing

Mint wallet links with your sandbox credentials and everything runs in test
mode end to end: the sheet shows test cards, attach completes instantly, and
pay-sheet mints skip the bank approval. See [Testing](/companies/testing) for
the sandbox setup.

## Instructions for your agent

Paste this into your coding agent to implement the embed.

```text Instructions for your agent theme={null}
You are embedding the Agentcard wallet in our web product. Our server already
authenticates users with Agentcard (we have their user_id — see
/companies/api/user-authentication). Base URL https://api.agentcard.sh.

1. SERVER: add an endpoint that mints a wallet link for the signed-in user.
POST /api/v2/wallet_links   (Authorization: Bearer <org client-credentials token>)
{ "user_id": "<id>", "expires_in": 900 }
-> 201 { id, url, expires_at, test_mode }
Return { url } to our frontend. Never expose org credentials to the browser;
the link URL itself is safe (short-lived, single-user capability).
-> 403 wallet_link_unavailable or 422 user_info_required: surface the message;
   the account needs setup steps first.

2. PAGE: load the SDK from Agentcard (never bundle it):
<script src="https://app.agentcard.sh/js/v1/agentcard.js"></script>

3. OPEN: fetch the link from our server, then:
const wallet = AgentCard.create({
  token: link.url,
  onSuccess: (d) => { /* refresh our UI */ },
  onTokenExpired: async () => wallet.update({ token: (await mintFresh()).url }),
});
wallet.open();
For a specific payment add: pay: { merchant: "<name>", amountCents: <int> }.

4. TRUTH: subscribe to webhooks (connection.created, connected_card.updated,
card.*) for durable state; treat SDK onSuccess as a UI signal only.

5. GOTCHAS:
- Our page origin must be registered with Agentcard for embedding; until it
  is, the sheet will refuse to render (frame-ancestors).
- Do not open the wallet in a sandboxed iframe of our own; the SDK manages
  its iframe itself.
- Bank approvals open in a new tab via the SDK; do not block window.open for
  this flow.
```
