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

# Message types

> The message Agentcard sends for each moment a shopper should hear about, and the helpers that tie a message to the link it ends.

A `ShopperMessage` is one moment of a purchase that a shopper should hear about, read from an Agentcard event and ready to post into that shopper's chat. `agentcard.listen` and `agentcardWebhook` hand your `onMessage` one for each such event, together with your own id for the shopper.

```ts messages.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 ?? '',
});

agentcard.listen({
  onMessage: (shopperId, message) => {
    if (message.kind === 'order_placed') recordOrder(shopperId, message.facts.orderId);
    return postToChat(shopperId, message.text);
  },
});
```

`recordOrder` and `postToChat` stand for your own code. The listener records each placed order and posts each message's `text` into the right chat. Give a `switch` over `kind` a default branch that sends `text`, because Agentcard can add a kind later.

## ShopperMessage

| Field | Type | Description |
| - | - | - |
| `eventId` | `string` | The event id. Agentcard delivers at least once: handle each id once. |
| `type` | `'purchase.message'`, `'order.confirmed'` or `'order.updated'` | The event type the message arrived as. |
| `kind` | `string` | The moment the message is about. See [Kinds](#kinds). |
| `userId` | `string` | The shopper's Agentcard user id. |
| `externalUserId` | `string` or `null` | Your own id for the shopper, when you gave Agentcard one: the chat to post `text` into. |
| `conversationId` | `string` or `null` | The buy conversation the event belongs to, when Agentcard names one. |
| `text` | `string` | Ready to send to the shopper as is. Agentcard writes it for a `purchase.message`, and the package writes it for `order_confirmed` and `order_updated`. |
| `note` | `string` | For your agent: the facts of what happened, marked as written by the system and not typed by the shopper. Empty when the message carries no note. |
| `facts` | `ShopperFacts` | The same values one by one, for a company that writes its own copy. |
| `advances` | `boolean` | `true` when the message ends a step the shopper took outside the chat. See [`buy.nextStep`](https://docs.agentcard.com/api-reference/commerce-sdk/turn#next-step). |
| `clientId` | `string` or `null` | The OAuth client id of the app the shopper connected through, when the event names it. |
| `livemode` | `boolean` | `false` for a sandbox event. |
| `created` | `number` | Unix seconds. |

Send `text` when you have no agent, and for every message that carries a link, so the link arrives exactly as Agentcard wrote it. Hand `note` to your agent as a turn when you want the agent to say it in its own voice. When a message has no `note`, or your agent cannot run the turn, send `text`.

## Kinds

`kind` is one of the values below. The last two columns say whether the message sets `advances`, and which link it ends.

| `kind` | When it arrives | `advances` | Ends |
| - | - | - | - |
| `order_placed` | The shopper approved, and Agentcard placed the order. | `false` | `approval`, `completed` |
| `order_failed` | The order failed after the shopper approved. | `false` | `approval`, `completed` |
| `order_not_placed_yet` | The shopper approved, and the order is not placed yet. | `false` | `approval`, `completed` |
| `order_unconfirmed` | The store's reply to the order was lost, so the order may exist, and Agentcard is confirming it. | `false` | `approval`, `completed` |
| `approval_expiring` | The approval link expires soon. `text` carries the link. | `false` | None |
| `approval_expired` | The approval expired before the shopper answered, and no charge went through. | `false` | `approval`, `expired` |
| `approval_declined` | The shopper declined the approval, and no charge went through. | `true` | `approval`, `declined` |
| `payment_declined` | The shopper approved, and the payment was refused before the order went in. `facts.reason` says why. | `false` | `approval`, `completed` |
| `card_link_unfinished` | The shopper has not added a card from their card link. `text` carries the link again. | `false` | None |
| `card_link_expired` | The card link expired before the shopper added a card. | `false` | `vault`, `expired` |
| `cart_idle` | A cart sat, and nobody sent it for approval. `facts.nudgeNumber` counts the nudges. | `false` | None |
| `card_added` | The shopper saved a card from the card link your app sent, or picked one they keep. | `true` | `vault`, `completed` |
| `store_connected` | The shopper signed in to their own account at the store from the sign-in link your app sent. | `true` | `store_login`, `completed` |
| `store_connect_failed` | The shopper's sign-in to the store did not go through, and the link no longer works. `text` offers a new one. | `false` | `store_login`, `failed` |
| `store_connect_expired` | The sign-in link expired before the shopper finished signing in. `text` offers a new one. | `false` | `store_login`, `expired` |
| `cart_ready` | The store's checkout built the cart an answer named as still being built. `text` gives the store's own total, and nothing is paid until the shopper says yes to it. | `false` | None |
| `cart_needs_input` | That checkout asks the shopper something first, such as a size, a delivery address, a sign-in to the store or an approval of the most the order can cost. | `false` | None |
| `cart_failed` | That checkout built no cart, and nothing was ordered or charged. | `false` | None |
| `order_confirmed` | The retailer accepted the order on its side, with its own order number and delivery window. | `false` | `approval`, `completed` |
| `order_updated` | The order reached a new delivery stage, named in `facts.orderStatus`. | `false` | None |

The first eighteen kinds arrive as `purchase.message` events. `order_confirmed` arrives as `order.confirmed`, and `order_updated` arrives as `order.updated`. A message whose `kind` is `approval_expiring` or `card_link_unfinished` leaves its link open.

## ShopperFacts

The values of a message one by one. A fact that does not apply to the message is absent.

| Field | Type | Description |
| - | - | - |
| `merchantName` | `string`, optional | The store's name. |
| `totalCents` | `number`, optional | The total in the currency's smallest unit: `2306` is `\$23.06`. |
| `currency` | `string`, optional | The three-letter currency code. |
| `cardBrand` | `string`, optional | The brand of the card. |
| `cardLast4` | `string`, optional | The last four digits of the card. |
| `orderId` | `string`, optional | Agentcard's order id. |
| `reason` | `string`, optional | Why an order failed or was not placed, or a payment was declined, in words. |
| `approvalUrl` | `string`, optional | The approval link. |
| `approvalExpiresAt` | `string`, optional | When the approval link stops working, as an ISO 8601 timestamp. |
| `cardLinkUrl` | `string`, optional | The card link. |
| `retryUntil` | `string`, optional | Until when Agentcard retries the order, as an ISO 8601 timestamp. |
| `validUntil` | `string`, optional | When the message stops holding, as an ISO 8601 timestamp: the expiry of any link or window its text names. A message that stays true carries none. |
| `nudgeNumber` | `number`, optional | `1` for a cart's first nudge, `2` for its second. |
| `retailerOrderId` | `string`, optional | The retailer's own order number. |
| `deliveryWindow` | `string`, optional | The retailer's quoted delivery window, in its own words. |
| `orderStatus` | `OrderStatus`, optional | The order's delivery stage. |
| `previousOrderStatus` | `OrderStatus`, optional | The stage before this one. |
| `statusTitle` | `string`, optional | The store's own line for the stage, such as "Your Dasher is nearby". |
| `arrivalEarliest` | `string`, optional | The earliest arrival the store estimates, as an ISO 8601 timestamp. |
| `arrivalLatest` | `string`, optional | The latest arrival the store estimates, as an ISO 8601 timestamp. |
| `deliveredAt` | `string`, optional | When the order was delivered, as an ISO 8601 timestamp. |
| `canceledReason` | `string`, optional | Why the store canceled the order, in its own word. |

A message is stale once `validUntil` has passed. Drop a stale message instead of sending it. `isStale(message)` checks it, and `listen` and `agentcardWebhook` do so for you.

### OrderStatus

`orderStatus` is one of `confirmed`, `courier_assigned`, `courier_at_store`, `ready_for_pickup`, `picked_up`, `arriving`, `delivered` or `canceled`. A stage Agentcard adds later reads as its own name.

## Event types and next-step kinds

| Constant | Value | Description |
| - | - | - |
| `SHOPPER_EVENT_TYPES` | `purchase.message`, `order.confirmed`, `order.updated` | The event types that carry a message for a shopper. Subscribe a webhook endpoint to each by name. |
| `NEXT_STEP_KINDS` | `card_added`, `store_connected`, `approval_declined` | The kinds after which your agent takes the next step on its own. Every other kind's `text` is the whole message: post it as is. |

## Action link helpers

A `vault`, `store_login` or `approval` link is a step the shopper takes outside the chat. Each step ends with a shopper message, and these helpers say which message ends which link.

| Export | Description |
| - | - |
| `ACTION_LINK_KINDS` | `['vault', 'store_login', 'approval']`. |
| `ACTION_LINK_ENDINGS` | For each kind of step, the message kinds that end it and how. The `Ends` column of [Kinds](#kinds) shows the same table. |
| `actionLinkId(link)` | Returns a stable id for a link, such as `vault_1x9k2qd`. The id is the same each time the same link is handed over, on the server and in the browser, and holds no part of the URL. |
| `actionLinkOutcome(link, message)` | Returns how `message` ends the step `link` is, or `null` when it leaves the step open. |
| `isActionLink(link)` | Returns `true` for a `vault`, `store_login` or `approval` link that has a page to open. |

`actionLinkOutcome` returns one of `completed`, `declined`, `failed` or `expired`. An approval is `completed` once the shopper approved, even when the order then failed or the payment was refused, because the approval page has nothing more to do.

The function returns `null` for a reminder such as `card_link_unfinished` or `approval_expiring`. `actionLinkOutcome` also returns `null` when the message's facts name another link of the same kind (`facts.approvalUrl` or `facts.cardLinkUrl`), or another store: a link that names its `store` stays open when `facts.merchantName` names a different one.

## Action windows

`actionWindows` comes from `@agent-cards/commerce-sdk/browser`. The function opens each step in a pop-up window centered over the chat, so the shopper never leaves it, and closes the pop-up the moment the message that ends the step arrives. A phone's browser opens the pop-up as a tab, and `settle` closes that tab the same way.

```ts chat.ts theme={null}
import { actionWindows } from '@agent-cards/commerce-sdk/browser';

const windows = actionWindows({
  onEnded: (step, outcome) => markStepDone(step.id, outcome),
  onClosed: (step) => keepOfferingStep(step.id),
});

// The link's own click handler, so the browser counts the tap as the reason for the pop-up.
stepLink.addEventListener('click', (event) => {
  if (windows.open(link)) event.preventDefault();
});

// Each shopper message your server forwards from listen or the webhook, with its kind and facts.
onShopperMessage((message) => windows.settle(message));
```

`markStepDone`, `keepOfferingStep`, `stepLink`, `link` and `onShopperMessage` stand for your own code. When the browser blocks the pop-up, `open` returns `null` and the link opens the way it would have, as a new tab.

### Options

```ts theme={null}
actionWindows(options?: ActionWindowsOptions): ActionWindows
```

| Option | Type | Description |
| - | - | - |
| `window` | `WindowLike`, optional | The page's window. Default: `globalThis.window`. Pass one to test with a fake. |
| `width` | `number`, optional | The pop-up's width in CSS pixels, kept inside the screen. Default `480`. |
| `height` | `number`, optional | The pop-up's height in CSS pixels, kept inside the screen. Default `760`. |
| `pollMs` | `number`, optional | How often to check whether the shopper closed a pop-up, in milliseconds. Default `500`. |
| `onEnded` | `(step: ActionWindow, outcome: ActionLinkOutcome) => void`, optional | Hears a step that ended while its pop-up was open, after `settle` closed the pop-up. Show the step as done here. |
| `onClosed` | `(step: ActionWindow) => void`, optional | Hears a pop-up the shopper closed before the step ended. The step is still open: keep offering its link. |

### Methods

| Method | Returns | Description |
| - | - | - |
| `open(link)` | `ActionWindow` or `null` | Opens the step's page in a pop-up, or brings its pop-up to the front when one is open already. Returns `null` when the link is not a step with a page, or when the browser blocked the pop-up. Call it from the tap's click handler. |
| `settle(message)` | `Array<{ step, outcome }>` | Closes each open pop-up whose step the message ends, and calls `onEnded` for each. Returns the steps it ended, or an empty list. |
| `get(link)` | `ActionWindow` or `null` | Returns the open pop-up for the link, or `null`. |
| `dispose()` | `void` | Stops watching every pop-up and leaves each one open, so a chat that remounts never shuts the page a shopper is typing a card into. |

`ActionWindow` has `id` (the link's `actionLinkId`), `kind`, `url`, `store` (`string` or `null`) and `open` (`false` once the window closed). The pop-up keeps its opener on purpose: a window opened with `noopener` hands back no handle, so the chat could neither close it nor tell when the shopper closed it.


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