agentcardWebhook(options) builds the handler for your webhook endpoint. The handler checks each delivery’s signature, skips an event it already handled, reads the message the event carries for a shopper, drops a message whose link or window has closed, and calls onMessage with your own id for the shopper. The handler answers Agentcard when your callback is done, so a failed post is delivered again.
server.ts
postToChat stands for your own code. shopperId is the externalUserId you gave the buy tool, so the message lands in the chat the purchase started in. Use the handler on a host where no process keeps running. On a server that keeps running, listen needs no endpoint.
Create the handler
TypeError when secret is empty, when onMessage is not a function, or when agentcard is not an AgentcardClient.
Pass
agentcard so the model reads each message through turn.messages(history). The Taking the next step guide shows what to do with a message whose advances is true.
The handler
AgentcardWebhookHandler is a function with a fetch member.
app/agentcard/webhooks/route.ts
fetch, so a Next.js route handler verifies and answers each delivery.
Raw body
The signature covers the exact bytes Agentcard sent, and a JSON parser that runs first throws those bytes away. Mount the route withexpress.raw before any app.use(express.json()). If a JSON parser has to run first, keep the bytes for the handler:
server.ts
req.rawBody, from a string or bytes in req.body, or from the unread request stream. Without the raw bytes, the handler answers 500 and onError receives the fix.
What each delivery answers
Agentcard waits 10 seconds for the answer, so keep
onMessage short: post the text, or queue the agent turn, then return. Agentcard delivers each event at least once, and again after any answer other than a 2xx.
The handler remembers the last 10,000 events it finished, so a redelivery of one runs nothing. A second delivery that lands while the first still runs waits for its answer. That memory belongs to one process: when more than one server takes deliveries, also skip a message.eventId you already stored.
Subscribe the endpoint
Subscribe your endpoint to each shopper event by name. A catch-all* alone does not turn purchase.message on. SHOPPER_EVENT_TYPES holds the three names: purchase.message, order.confirmed and order.updated.
Create the endpoint with Create a webhook endpoint. The Webhooks overview describes the envelope, and Order events describes the order events.
purchaseAgentWebhook
purchase.message events only and leaves skipping repeated events to you. New code takes agentcardWebhook. An empty secret throws when the server starts.
Verify a delivery
AgentCard-Signature header and the endpoint’s secret. The function reads nothing in the body before the signature holds. The header reads t=<unix seconds>,v1=<64 hex characters>.
verify.ts
message holds the shopper message or null.
The function throws AgentcardSignatureError when the delivery did not come from Agentcard. error.reason says why.
A correctly signed body that is not an Agentcard event throws a
TypeError. Empty secret and a body that is neither a string nor bytes throw a TypeError too.
AgentcardEvent
The envelope every event arrives in. Delivery is at least once: deduplicate onid.
Read a message
readShopperMessage
null when it carries none: an event type that is not a shopper event, an order event that names no shopper, or a delivery stage the package does not know and the store gave no line for. Pass the event verifyAgentcardWebhook returned. The function throws a TypeError when a purchase.message lacks its kind, user_id or text.
readPurchaseMessage
purchase.message event carries, or null for any other event type. A PurchaseMessage has the fields of a ShopperMessage except type, conversationId and advances. The function throws a TypeError under the same condition as readShopperMessage.
isStale
true when the message stopped holding before now, in Unix milliseconds. The default is the system clock. A message stops holding when the link or window its text names has closed, as facts.validUntil says. Drop a stale message instead of sending it.