Skip to main content
After you start a checkout, your app must watch it until it ends. You can read the checkout, or read its messages. The checkout gives its status and the question that waits for an answer (requiredAction). The messages give the full conversation, the progress, and the result. A first integration reads the checkout every 2 seconds. A production integration streams the messages.

Prerequisites

  • Checkout integration — complete the quickstart.
  • Server key — a production key with agent-checkouts.read and agent-checkouts.update. Add agent-checkouts.cancel to cancel checkouts.
  • One user ID — use the same x-crossmint-user-id that created the checkout.

Authenticate Each Request

For a backend integration, keep the server key outside client bundles and send the buyer’s identifier on every call:
For browser or native calls, use a production client-side key and Authorization: Bearer YOUR_USER_JWT instead of x-crossmint-user-id. Register your JWT issuer under 3P Auth providers in the Crossmint Console and allow your app’s origins. The request bodies are the same.

Observe and Recover

1

Choose the observation strategy

Crossmint recommends streaming for live production integrations. The quickstart uses polling to keep its example small.
2

Open the message stream

The stream is an HTTP response with content type text/event-stream. It carries both checkout snapshots (run.updated) and message changes:
cURL
Store each event’s id as the resume cursor. Keep the greatest checkout revision, and the greatest message revision for each message ID. Replayed or stale snapshots must not replace newer state. Revisions can skip; they order snapshots, while the cursor identifies the stream replay position.Handle open requests once by requestId, whether they arrive in requiredAction or a message part. Dispatch interaction.kind: "form" to Answer Questions, and "payment" to Pay for a Checkout.Reconnect with the cursor in the last-event-id header or after query parameter. If the cursor is rejected or recovery needs a fresh snapshot, use the next step.
3

Bootstrap or resynchronize from history

List all message pages before you open a stream for an existing checkout. The final page’s streamCursor provides the handoff to live updates. Read the checkout after the history, so that you also find a question that waits for an answer.
Show the recovered messages and the current checkout. If run.status is "awaiting_input" and its request has not been answered, handle run.requiredAction before connecting to streamUrl with the same authentication. Retain your request deduplication state across reconnects. See List Messages and Stream Messages for pagination and replay details.
4

Poll when streaming is unavailable

Read GET /{runId} with the same headers and cache: "no-store". Poll every 2 seconds while active, increase the interval while nothing changes, and reset it after a status change or response. Stop at a terminal status.Retry transient read failures with bounded backoff. For 429, respect the response’s retryAfterSeconds. This read helper illustrates the retry behavior:
A poll immediately after a successful response can still contain the same action. Track answered request IDs so you do not submit again. For resumed integrations, recover message history rather than relying only on an in-memory set.

Process Message Parts

A message can contain several parts. Store it by id, retain its greatest revision, and use role to identify the speaker. Your UI can also display the read-only checkout browser in an iframe using run.browser.embedUrl. browser is null until a session exists; displaying it is optional.

Respond or Steer

Submit the form answer or payment authorization to POST /{runId}/messages. Generate the message id once and retain its exact body until acknowledged. If acknowledgement is lost, retry the same ID and body. After a definitive validation rejection, correct the response with a new ID. Do not reuse an ID for changed content. Respond before the request’s expiresAt. If it closes or expires, load the current request. After a response, continue observing: the agent may ask several questions during a purchase. To choose another path, send an alternative instead of the requested answer:
Use action: "decline" without text to refuse a request. Do not use a text part to answer an open question: send a form response for its requestId. To steer without answering a particular request, send a text part:
A 202 Accepted confirms message acceptance, not that the agent consumed it. Follow its delivery and the later checkout updates.

Finish or Cancel

For a succeeded checkout with result.purchase.kind: "receipt_captured", read result.purchase.receipt for the total and merchant order ID. The result message supplies the user-facing summary. Cancel an active checkout with POST /{runId}/cancel, using the same authentication. It returns 202 Accepted. Keep watching until the checkout reaches a final status. See Statuses, Results, and Errors. The stream closes after terminal updates.

Connect Your User Channel

Your application associates each handoff with the buyer, the checkout, and the question. See Answer Questions for combining answers collected across channels.

Next Steps

Answer Questions

Show standard and protected fields and send the answers

Statuses, Results, and Errors

Handle every result of a checkout

Stream Messages

Review the stream and its message types