Checkout flow

POST /v1/quotes (one per item) ─▶ qt_  (price locked 15 min)
POST /v1/checkouts { items } ─▶ chk_ pending_payment ──(user pays once on Stripe)──▶ paid ─▶ placing ─▶ confirmed
                                       │                                                     ├─▶ partially_confirmed (failed items refunded)
                                       ├─▶ expired (link expired / session expired)          └─▶ failed ─▶ refunded
                                       └─▶ cancelled (agent cancelled before payment)

Quote

POST /v1/quotes { variantId, countryCode? } calls Snappy's live availability for the country (US today) and computes:

item + shipping + duties (Snappy, per country)  +  service fee (admin setting, default 0)  =  totalAmount

Quotes expire after 15 minutes. A checkout against an expired quote returns 409 quote_expired; create a new quote and tell the user if the price changed.

Checkout (cart)

POST /v1/checkouts takes items: [{ quoteId, quantity? }] (up to 20 lines, quantity 1-10 each, all quotes for the same country) and validates everything that can be validated before money moves: quote freshness and ownership, address completeness and Snappy verification for physical items, country consistency, E.164 phone, SMS consent, user status, maintenance mode. Only then it creates one Stripe Checkout Session with one line per item and returns paymentUrl.

Request fields:

Field Notes
items[] Quotes to buy; quantity expands into that many Snappy orders of the same variant
recipient Who receives the package (buyer or someone else). phone is used by carriers.
shippingAddress Full address when any item is physical; { countryCode } only for digital-only carts
notify.email, notify.sms Where to send confirmation and receipt. Email defaults to the buyer; null disables. SMS requires consent (below).
smsConsent true only after the user agreed to the consent text; recorded with timestamp, source and client
callbackUrl Optional HTTPS endpoint for signed status pushes
idempotencyKey Optional; returns the existing open checkout for the same cart

Payment

The user opens paymentUrl (Stripe Checkout). The link expires with the checkout (30 minutes). Stripe calls /webhooks/stripe; checkout.session.completed with payment_status=paid moves the checkout to paid and enqueues order placement. The paid amount and currency are checked against the quote; a mismatch is refunded automatically and never placed.

Placement

The place-order task calls POST /v3/orders once per item with idempotencyKey = <checkout id>:<item index>, so retries after a crash or a 5xx cannot double-order. Each placed item becomes an ord_ row. When every item is placed the checkout is confirmed; when some items are definitively rejected by Snappy (4xx such as out of stock) the checkout is partially_confirmed, the rejected items' amounts are refunded automatically and the confirmation email says which ones; when nothing could be placed the checkout is failed and refunded in full. Operators see every failure on the dashboard.

Watching the result

Method Use
GET /v1/checkouts/{id} Poll; nextStep tells the agent what to say
GET /v1/checkouts/{id}/events SSE; replays history, resumes with Last-Event-ID, ends with event: done
GET /v1/checkouts/{id}/timeline Full event list
callbackUrl Signed POST on checkout.confirmed / checkout.partially_confirmed / checkout.failed with the list of orders

Callback signature

X-Snappy-Agents-Timestamp: 1733942400
X-Snappy-Agents-Signature: sha256=HMAC_SHA256(secret, timestamp + "." + body)

The secret is provided to the agent platform out of band at onboarding.

After the purchase

Support requests (returns, damaged, wrong item, not received)

POST /v1/orders/{id}/support-requests { type, summary, details?, preferredResolution?, contactEmail?, contactPhone? } opens a structured request. Snappy support is emailed with the full context, the user receives an acknowledgement, and operators resolve it in the admin portal: partial or full refund (issued on the original payment), replacement, or rejection, each with a note that is emailed to the user. The agent can list requests with GET /v1/orders/{id}/support-requests or GET /v1/support-requests. One open request per order.

SMS consent

Texting a US number requires prior express consent (TCPA/CTIA). The platform records who consented, when, through which agent and to which text; the first message carries "Reply STOP to opt out"; inbound STOP/START/HELP are handled on /webhooks/twilio and opted-out numbers are never texted again (sms_opted_out). Sender identity is a Twilio Messaging Service registered for A2P 10DLC (configured in Integrations).

Snappy Agents · agents.snappy.com · support@snappy.com