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

# Web

> Add the Agentcard wallet to any web app with a script tag and one call.

This is the wallet for web apps. You load one script, pass it a wallet link, and the wallet opens as a sheet over your page. Card entry happens inside our iframe, so card numbers never touch your code.

## Register your web origins

The embed only opens on origins you've registered. Register them once from your server:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT https://api.agentcard.sh/api/v2/embed_origins \
    -H "Authorization: Bearer $ORG_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"origins": ["https://app.example.com", "http://localhost:3000"]}'
  ```

  ```javascript Node theme={null}
  const res = await fetch("https://api.agentcard.sh/api/v2/embed_origins", {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${ORG_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ origins: ["https://app.example.com", "http://localhost:3000"] }),
  });
  const data = await res.json();
  ```

  ```python Python theme={null}
  import requests

  res = requests.put(
      "https://api.agentcard.sh/api/v2/embed_origins",
      headers={"Authorization": f"Bearer {ORG_TOKEN}"},
      json={"origins": ["https://app.example.com", "http://localhost:3000"]},
  )
  data = res.json()
  ```
</CodeGroup>

`PUT` replaces the whole list and `GET` reads it back. Up to 10 origins, `https` only, except that `http` is allowed for `localhost`, `127.0.0.1`, and `[::1]` while you develop. Values are normalized to their origin (scheme, host, port), and changes take effect within about five minutes. On an unregistered origin the wallet doesn't open at all, so this is the first thing to check if you see a blank frame.

## Create a wallet link (server side)

The wallet needs a link for the user who is opening it. Create one from your server:

<CodeGroup>
  ```bash cURL 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": "USER_ID"}'
  ```

  ```javascript Node theme={null}
  const res = await fetch("https://api.agentcard.sh/api/v2/wallet_links", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${ORG_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ user_id: "USER_ID" }),
  });
  const link = await res.json();
  ```

  ```python Python theme={null}
  import requests

  res = requests.post(
      "https://api.agentcard.sh/api/v2/wallet_links",
      headers={"Authorization": f"Bearer {ORG_TOKEN}"},
      json={"user_id": "USER_ID"},
  )
  link = res.json()
  ```
</CodeGroup>

The response includes a `url`. Hand that to your frontend. Links expire after 15 minutes by default, and you can pass `expires_in` (60 to 86400 seconds) to change that.

## Open the wallet

Your backend creates the wallet link; your page fetches it from you and hands it to the wallet:

```html theme={null}
<script src="https://app.agentcard.sh/js/v1/agentcard.js"></script>
<script>
  async function openWallet() {
    // your endpoint from the previous step, returning the wallet_link JSON
    const link = await fetch("/api/agentcard/wallet-link").then((r) => r.json());

    const wallet = AgentCard.create({
      token: link.url,
      onSuccess: (data) => {
        // data.type is "card_attached" or "payment_completed"
      },
      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");  // render inline in your page
  }
</script>
```

Load the script from our URL. Don't bundle it; that's how you keep getting fixes without shipping updates.

To ask for a payment instead of opening the wallet home, pass `pay`:

```js theme={null}
AgentCard.create({
  token: link.url,
  pay: { amountCents: 500, merchant: "Amazon" },
  onSuccess: (data) => {},
}).open();
```

The other methods: `update(next)` swaps the token or pay intent in place, `exit()` closes the sheet, and `destroy()` removes everything. Every bridge event also reaches `onEvent(name, data)` if you want analytics.

## Webhooks you will receive

The SDK events are UI signals. Your server should trust webhooks instead:

* `connected_card.updated` when the user adds a card
* `transaction.authorized` and the other `transaction.*` events when payments happen

## When it fails

If the wallet link expired, the sheet says so and `onTokenExpired` fires. Create a fresh link and call `wallet.update({ token })`.

Bank approvals sometimes need a popup. If the browser blocks it, the SDK shows an approval bar inside the sheet that the user can tap instead. You can take over that behavior with `onOpenUrl(url)`.

## Sandbox behavior

With sandbox credentials, the connect code is always `111111` and the test card is `4242 4242 4242 4242` with any future expiry and any CVC. No real money moves.

Next: [Connect users](/wallet/connect-users)
