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

# listen

> Hears every event Agentcard sends your company over one connection your server opens, with no webhook endpoint.

`agentcard.listen(options)` opens one connection from your server to Agentcard with your company credentials. Agentcard sends every event for your company down it, so you need no public URL, no webhook secret and no signature check. Each event is the envelope a webhook endpoint would receive, with the same `id`.

```ts server.ts theme={null}
import { AgentcardClient } from '@agent-cards/commerce-sdk';

const agentcard = new AgentcardClient({
  clientId: process.env.AGENTCARD_CLIENT_ID ?? '',
  clientSecret: process.env.AGENTCARD_CLIENT_SECRET ?? '',
});

const listener = agentcard.listen({
  onMessage: (shopperId, message) => postToChat(shopperId, message.text),
  onEvent: (event) => console.log(event.type, event.data),
});

await listener.ready;
```

`postToChat` stands for your own code. `onMessage` posts each shopper message into the right chat, `onEvent` logs every other event, and `ready` resolves once the listener reaches Agentcard. The [Quickstart](https://docs.agentcard.com/commerce-sdk/quickstart.md) page shows where `listen` fits in a server.

`listen` needs a process that keeps running, such as a Node server, a worker or a container. On a host that ends each request after a few seconds, such as a function the host starts for each request, take the events at a [webhook endpoint](https://docs.agentcard.com/api-reference/commerce-sdk/webhook-handler.md) instead.

## Start listening

```ts theme={null}
agentcard.listen(options: ListenOptions): AgentcardListener
listenToAgentcard(agentcard: AgentcardClient, options: ListenOptions): AgentcardListener
```

Both forms do the same thing, and the method calls the function. The call returns at once. The function throws a `TypeError` when the client was built with a shopper's access token, when you pass neither `onMessage` nor `onEvent`, when `events` is empty or names something Agentcard does not send, or when `transport` is not `auto`, `stream` or `poll`.

Run one listener per company and mode. Two listeners each hear every event, so with two, skip a `message.eventId` you already stored. Sandbox credentials hear sandbox events, live credentials hear live ones, and a shopper message is always live.

## Options

| Option | Type | Description |
| - | - | - |
| `onMessage` | `(externalUserId, message, event) => void` or a promise, optional | Receives each event for one of your shoppers, under your own id for them, with the message ready to post into that shopper's chat. |
| `onEvent` | `(event: AgentcardEvent) => void` or a promise, optional | Receives every event `onMessage` does not take. Without `onMessage`, it receives every event. |
| `onError` | `(error, event?) => void` or a promise, optional | Receives what went wrong. Default: `console.error`. A reporter that throws is ignored. |
| `events` | `string[]`, optional | Only these events: event types, families such as `order.*`, or `*`. Default: every event. |
| `cursor` | `ListenCursorStore`, optional | Keeps the cursor across restarts. Default: in memory, so a new process starts from now. |
| `transport` | `'auto'`, `'stream'` or `'poll'`, optional | `auto` streams and falls back to polling when the stream is not available. Default `auto`. |
| `pollIntervalMs` | `number`, optional | How long polling waits between reads. Default `2000`. |
| `heartbeatTimeoutMs` | `number`, optional | How long the stream may stay silent before the listener reconnects. Default `45000`. |
| `pollTimeoutMs` | `number`, optional | How long one polling read may take before the listener gives up on it and reads again. Default `30000`. |
| `fetch` | `StreamFetch`, optional | The fetch to stream with. Default: the global `fetch`. |

### `onMessage` and `onEvent`

`onMessage` gets the same ready-to-send message as [`agentcardWebhook`](https://docs.agentcard.com/api-reference/commerce-sdk/webhook-handler): a card saved, an order placed, an approval about to expire, a delivery on its way. See [Message types](https://docs.agentcard.com/api-reference/commerce-sdk/messages.md) for the fields. `onEvent` gets every other event, such as `vault.card_stored` or `checkout_authorization.approved`.

A message goes to `onEvent` instead when it is for a shopper you gave Agentcard no id for, or when its link or window closed before it arrived. Once `onMessage` returns, the client keeps the message for that shopper's next `buy.turn`, which hands it to your model. A message whose `onMessage` threw every time never reached the chat, so the client does not keep it.

## Return value

`listen` returns an `AgentcardListener`.

| Member | Type | Description |
| - | - | - |
| `ready` | `Promise<void>` | Resolves once the listener first reaches Agentcard, or once it closes. |
| `transport` | `'stream'` or `'poll'` | How the listener hears Agentcard right now. |
| `cursor` | `string` or `null` | The cursor of the last event handled, or `null` before the first connection. |
| `close()` | `Promise<void>` | Stops listening. Resolves once the callback that is running, if any, has returned. |

## Delivery rules

Events run one at a time, in the order Agentcard recorded them. The listener waits for your callback before it hands over the next event.

| Behavior | Value |
| - | - |
| Callback retries | A callback that throws runs again, up to five tries in all, waiting 1, 2, 4 and 8 seconds between tries. After the fifth, the listener passes the error to `onError` and moves on. |
| Reconnect | After a dropped connection, the listener waits longer after each failure, up to 30 seconds. |
| Heartbeat | Agentcard sends a heartbeat every 15 seconds. The listener reconnects when none arrives within `heartbeatTimeoutMs`. |
| Resume | Each reconnect resumes after the last event the listener handled, so nothing that happened while it was away is lost. |
| Duplicates | The listener remembers the last 10,000 event ids it handled and skips an event it already handled. |
| Retention | Agentcard keeps each event for the stream for 72 hours. |
| Polling | Where the stream does not get through, the listener reads the same events every `pollIntervalMs`. After three streams in a row that answered but never said ready, `auto` polls instead. |

A cursor Agentcard no longer keeps goes to `onError`, and the listener starts from now.

## Resume across restarts

A new process starts from the moment it connects. Pass a `cursor` store to resume where the last process stopped.

```ts theme={null}
interface ListenCursorStore {
  load(): Promise<string | null> | string | null;
  save(cursor: string): Promise<void> | void;
}
```

`load` returns the cursor saved last, or `null` to start from now. `save` runs after each handled event, and at most once a minute while no event arrives. Treat the saved string as opaque.

After a resume, Agentcard hands over the last 10 seconds of events again, in case one of them arrived late. The listener saves the cursor with the id of each event it handled in that window, so a new process skips the ones it already handled.

```ts server.ts theme={null}
agentcard.listen({
  onMessage: (shopperId, message) => postToChat(shopperId, message.text),
  cursor: {
    load: () => redis.get('agentcard:cursor'),
    save: (cursor) => redis.set('agentcard:cursor', cursor),
  },
});
```

`redis` stands for your own store. A restarted server resumes after the last event it handled.

## Event types

`AGENTCARD_EVENT_TYPES` lists every event type Agentcard sends your company, the same list a webhook endpoint subscribes to by name. `AgentcardEventType` is the union of those names. The tables below link each event that has its own page.

### Purchases and orders

| Event | Description |
| - | - |
| `purchase.message` | A moment of a purchase that a shopper should hear about, such as a saved card or an approval about to expire. |
| [`order.placed`](https://docs.agentcard.com/webhooks/orders/order-placed) | An order was placed at the merchant. |
| [`order.confirmed`](https://docs.agentcard.com/webhooks/orders/order-confirmed) | The merchant accepted the order on its side. |
| [`order.failed`](https://docs.agentcard.com/webhooks/orders/order-failed) | A confirm ended without a placed order. |
| [`order.updated`](https://docs.agentcard.com/webhooks/orders/order-updated) | A placed order moved to a new delivery stage. |
| [`merchant.connected`](https://docs.agentcard.com/webhooks/rewards/merchant-connected) | The shopper linked a merchant account, such as DoorDash. |

The Orders events are covered in [Order events](https://docs.agentcard.com/webhooks/orders/overview.md). `purchase.message`, `order.confirmed` and `order.updated` carry a message for a shopper, so `onMessage` receives them. Subscribe a webhook endpoint to each by name.

### Vault and checkout authorizations

| Event | Description |
| - | - |
| [`vault.session_linked`](https://docs.agentcard.com/webhooks/vault/vault-session_linked) | An open vault session got its user. |
| [`vault.card_stored`](https://docs.agentcard.com/webhooks/vault/vault-card_stored) | The shopper stored a card in their vault. |
| [`vault.payment_permission.updated`](https://docs.agentcard.com/webhooks/vault/vault-payment_permission-updated) | A shopper's permission for your app to pay with one of their cards changed. |
| [`checkout_authorization.approved`](https://docs.agentcard.com/webhooks/checkout-authorizations/checkout_authorization-approved) | The shopper approved with their passkey and the processor answered. |
| [`checkout_authorization.submitted`](https://docs.agentcard.com/webhooks/checkout-authorizations/checkout_authorization-submitted) | For hosted-form processors, instead of `approved`: the shopper's device attested that it submitted the form. |
| [`checkout_authorization.declined`](https://docs.agentcard.com/webhooks/checkout-authorizations/checkout_authorization-declined) | The shopper said no, a check refused it, or the processor rejected the request. |
| [`checkout_authorization.expired`](https://docs.agentcard.com/webhooks/checkout-authorizations/checkout_authorization-expired) | Nobody approved within 15 minutes. |
| [`checkout_authorization.refused`](https://docs.agentcard.com/webhooks/checkout-authorizations/checkout_authorization-refused) | One of your presets refused a purchase before anyone was asked to approve it. |
| [`checkout_authorization.watched`](https://docs.agentcard.com/webhooks/checkout-authorizations/checkout_authorization-watched) | A finished purchase broke a rule one of your presets watches. |
| [`checkout_authorization.amount_mismatch`](https://docs.agentcard.com/webhooks/checkout-authorizations/checkout_authorization-amount_mismatch) | The charge disagreed with the approval. |
| [`checkout_authorization.settled`](https://docs.agentcard.com/webhooks/checkout-authorizations/checkout_authorization-settled) | The payment settled. |
| [`checkout_authorization.settlement_unconfirmed`](https://docs.agentcard.com/webhooks/checkout-authorizations/checkout_authorization-settlement_unconfirmed) | Agentcard's read of the payment rested anywhere but settled. |

### Cards and connections

| Event | Description |
| - | - |
| [`card.created`](https://docs.agentcard.com/webhooks/cards/card-created) | A one-time virtual card was created. |
| [`card.updated`](https://docs.agentcard.com/webhooks/cards/card-updated) | The card's balance or status changed. |
| [`card.closed`](https://docs.agentcard.com/webhooks/cards/card-closed) | The card closed. |
| [`transaction.authorized`](https://docs.agentcard.com/webhooks/cards/transaction-authorized) | A merchant authorized a charge on the card. |
| [`transaction.cleared`](https://docs.agentcard.com/webhooks/cards/transaction-cleared) | The authorization settled. |
| [`transaction.declined`](https://docs.agentcard.com/webhooks/cards/transaction-declined) | A charge was declined. |
| [`transaction.voided`](https://docs.agentcard.com/webhooks/cards/transaction-voided) | A pending authorization was reversed by the merchant. |
| [`balance.low`](https://docs.agentcard.com/webhooks/cards/balance-low) | A card's remaining balance dropped below its threshold. |
| [`connection.created`](https://docs.agentcard.com/webhooks/connections/connection-created) | A shopper finished connecting to your company. |
| [`approval.requested`](https://docs.agentcard.com/webhooks/connections/approval-requested) | An action on a shopper's card needs their approval. |
| [`identity.verification.updated`](https://docs.agentcard.com/webhooks/identity-verification/identity-verification-updated) | A shopper's identity verification moved to a new status. |
| [`reward.earned`](https://docs.agentcard.com/webhooks/rewards/reward-earned) | The shopper earned Agentcard points on a transaction. |
| [`reward.reversed`](https://docs.agentcard.com/webhooks/rewards/reward-reversed) | Points were reversed, for a refund or a voided transaction. |

### Company balance

| Event | Description |
| - | - |
| [`cardholder.created`](https://docs.agentcard.com/webhooks/company-wallet/cardholder-created) | A cardholder was created for your company. |
| [`cardholder_onboarding_session.completed`](https://docs.agentcard.com/webhooks/company-wallet/cardholder_onboarding_session-completed) | A hosted onboarding session finished and the cardholder exists. |
| [`card_flow.started`](https://docs.agentcard.com/webhooks/company-wallet/card_flow-started) | A company-funded card mint started. |
| [`card_flow.failed`](https://docs.agentcard.com/webhooks/company-wallet/card_flow-failed) | The mint did not complete. |
| [`transfer.approved`](https://docs.agentcard.com/webhooks/company-wallet/transfer-approved) | A transfer was approved. |
| [`transfer.completed`](https://docs.agentcard.com/webhooks/company-wallet/transfer-completed) | The funds reached the cardholder. |
| [`wallet.funded`](https://docs.agentcard.com/webhooks/company-wallet/wallet-funded) | The company balance received funds. |
| [`wallet.balance.low`](https://docs.agentcard.com/webhooks/company-wallet/wallet-balance-low) | The company balance dropped below the threshold you set. |
| [`recovery.requested`](https://docs.agentcard.com/webhooks/company-wallet/recovery-requested) | Residual spending power on a cardholder is being recovered. |

### Other types in the list

`AGENTCARD_EVENT_TYPES` also holds these types, which have no page of their own yet: `cardholder.updated`, `connected_card.updated`, `vault.session_card_picked`, `action.confirmed`, `action.done`, `checkout_authorization.cancelled`, `checkout_authorization.duplicate_charge`, `checkout_authorization.outcome_reported`, `transfer.initiated`, `transfer.failed`, `transfer.released`, `user_wallet.funding_detected`, `user_wallet.funded`, `recovery.completed`, `recovery.rejected` and `wallet_link.opened`.

A type Agentcard adds later reaches `onEvent` under its own name. The [Webhooks tab](https://docs.agentcard.com/webhooks/overview.md) describes how each event's payload reads.


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