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

# Checking out inside your app

> Open the Vault and approval pages in an in-app browser, and send the shopper back into your app the moment they finish.

Your iPhone app opens the Vault page or the approval page in an in-app browser. The shopper saves a card or approves the payment with Face ID, and the page sends them straight back into your app at the link you chose, such as `yourapp://approved`. Your app never sends the shopper to Safari, and the shopper never has to find their way back.

Pass one option, `returnUrl`, when you build the tool:

```ts agentcard.ts theme={null}
const buy = buyTool({
  agentcard,
  externalUserId: viewer.id,
  returnUrl: 'yourapp://agentcard-done',
});
```

Every link the tool hands to `sendLink` from then on ends at `yourapp://agentcard-done`: the Vault link for a shopper who has no card yet, and the approval link for each purchase.

## Register your app's link

Agentcard sends a shopper only to a link your company registered, so a link copied into a request from anywhere else goes nowhere. Add the exact link, such as `yourapp://agentcard-done`, to your sandbox credentials and your production credentials alike. In the dashboard, open `Settings → Developers → Credentials`, add the link under `Redirect URIs` on the `Sandbox` tab, then add it again on the `Production` tab. Your app's own scheme works, and so does `https`. An `http` link works only on `localhost`, in sandbox.

Agentcard refuses an unregistered link before it starts the purchase, and the call throws an `AgentcardApiError` with `code: invalid_return_url`:

```text theme={null}
AgentcardApiError: return_url must be exactly one of this client's registered redirect URIs (scheme and host compared without case, a default port dropped, the fragment ignored, path and query as registered); http only on localhost in test mode.
```

| `code` | `status` | What it means | What to do next |
| - | - | - | - |
| `invalid_return_url` | `400` | The `returnUrl` on the request is not one of the links your company registered. | Register that exact link on the credentials the request used, and send the request again. |

## Open the page in an in-app browser

Open each `vault` and `approval` link in `SFSafariViewController` or `ASWebAuthenticationSession`. Both run Safari, so the shopper's passkey works there with Face ID or Touch ID.

A `WKWebView` inside your app cannot use the shopper's passkey. The page then shows the shopper a button that opens it in Safari instead, and the shopper returns to your app from there. Use one of the two Safari views to keep the shopper inside your app.

```ts chat.ts theme={null}
const turn = buy.turn(message, {
  sendLink: (link) => {
    if (link.kind === 'vault' || link.kind === 'approval') return openInAppBrowser(viewer.id, link.url); // your code
    return deliverLink(viewer.id, link);
  },
});
```

## Read what the shopper did

The page sends the shopper to your link with what happened on it, so your app can close the browser and move on without asking Agentcard.

| Page | Your app opens |
| - | - |
| Vault, once the card is saved | `yourapp://agentcard-done` |
| Approval, once the shopper approves | `yourapp://agentcard-done?authorization_id=cauth_3f9a…&status=approved` |
| Approval, once the shopper declines | `yourapp://agentcard-done?authorization_id=cauth_3f9a…&status=declined` |

A registered link that already carries a query, such as `yourapp://done?from=agentcard`, comes back exactly as registered, with nothing added.

`ASWebAuthenticationSession` hands the link to its completion handler and closes on its own when you start it with your link's scheme. With `SFSafariViewController`, your app's link handler receives it, and your app dismisses the view. Agentcard still tells your server what happened, and the chat moves on from those messages. `card_added` arrives once the card is saved, and `order_placed` once the order goes through, through `listen` or the webhook handler, as [Adding a card](https://docs.agentcard.com/commerce-sdk/guides/adding-a-card.md) and [Approving a purchase](https://docs.agentcard.com/commerce-sdk/guides/approving-a-purchase.md) show. Post each message's `text` into the chat as it arrives.

### Carry your own state through the Vault

Make the Vault link yourself when your app wants a value of its own back, such as the moment on screen the shopper was buying from:

```ts vault.ts theme={null}
const link = await agentcard.mintVaultLink({
  externalUserId: viewer.id,
  returnUrl: 'yourapp://agentcard-done',
  state: 'moment-7',
});
// The shopper returns to yourapp://agentcard-done?state=moment-7
```

`state` needs `returnUrl`. A `state` without one throws a `TypeError`, and no request is sent.

## Check it in the sandbox

1. Register `yourapp://agentcard-done` on your sandbox credentials.
2. Build the tool with `returnUrl`, and send "two packs of AA batteries from Amazon" as a test shopper nobody connected.
3. Open the `vault` link in `SFSafariViewController`, and save a test card with Face ID or Touch ID. A shopper nobody connected saves their card first, so the link asks for no code. Check that your app opens `yourapp://agentcard-done` and the browser closes.
4. Send a `returnUrl` you did not register. Check that the call throws `invalid_return_url`.

The sandbox stops before payment, so the approval page and its return open only with live credentials. Check them by buying one inexpensive item with your own card, as [Approving a purchase](https://docs.agentcard.com/commerce-sdk/guides/approving-a-purchase.md#check-it-with-live-credentials) describes.


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