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.readandagent-checkouts.update. Addagent-checkouts.cancelto cancel checkouts. - One user ID — use the same
x-crossmint-user-idthat 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: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 Store each event’s
text/event-stream. It carries both checkout snapshots (run.updated) and message changes:cURL
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 Show the recovered messages and the current checkout. If
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.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 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.
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:Process Message Parts
A message can contain several parts. Store it byid, 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 toPOST /{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:
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:
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

