Cart states
A cart's status field is the single source of truth for whether it can still accept mutations. Only open carts are writable.
State machine
stateDiagram-v2
[*] --> open: POST /carts
open --> open: mutations (items / shipping / discount / tax)
open --> checkout: POST /carts/{id}/checkout (creates draft order)
checkout --> converted: order is paid (payment.captured)
open --> abandoned: 24h idle (background sweep)
checkout --> abandoned: 24h idle OR order cancelled before payment
abandoned --> [*]
converted --> [*]States
open— initial state. Accepts items, discount apply/remove, shipping select, tax recompute, and checkout conversion.checkout— cart has been converted to a draft order (POST/carts/{id}/checkout). The cart is frozen; further mutations return 409cart_already_checked_out.converted— the linked draft order was paid and an order row was finalised (payment.capturedfired). Read- only; archived.abandoned— no activity for 24 hours (or past the cart'sexpiresAt), OR the linked draft order was cancelled before payment. An hourly background sweep performs the idle transition and emitscart.abandoned. Read-only. See Abandoned-cart recovery below.
Mutation rules
- Every mutation increments
sequenceon the cart. Webhook payloads include sequence so subscribers can detect out-of-order delivery. - POST
/carts/{id}/checkouttransitionsopen→checkout. Idempotent within the 24h Idempotency-Key window — same key replays the same draft order id. - Direct transitions out of
checkout/converted/abandonedvia the cart API are not allowed. Cancel the linked order to roll the cart back toabandoned.
Error codes
Mutating a non-open cart returns one of:
cart_already_checked_out (409), cart_already_converted (409), or cart_abandoned (410).Abandoned-cart recovery
An hourly background sweep marks idle carts as abandoned and emits cart.abandoned. A cart is idle when it has had no mutation for 24 hours — every item, shipping, discount, or tax change resets the clock — or when its expiresAt has passed. Empty carts and externally-managed carts are never swept.
- Webhook (always): every swept cart emits
cart.abandonedto your subscribed webhook endpoints, so you can drive recovery from your own systems — including for anonymous carts you track by your own session. The payload carries the line items, totals,customer(a linked customer, or a guest withid: nullwhen only the cart’scustomerEmailwas captured;nullif neither),shippingAddress/billingAddress(if set), and therecoveryUrl. - Recovery email (when configured): Throttle sends the
customer.cart_abandonedemail automatically when the cart has a known customer email and the application has acartRecoveryUrlTemplateset. The template is a storefront URL containing a{cartId}placeholder, e.g.https://shop.example.com/cart?c={cartId}. The email respects customer email opt-out / unsubscribe; in non-production environments it carries a[TEST]subject banner.
Create the cart at add-to-cart to enable recovery
Throttle can only recover carts it knows about. To get abandoned-cart recovery, create the Throttle cart (POST
/carts) when the buyer first adds an item and mutate it on every change — the mirror pattern. If you only create the cart at checkout, abandonment happens before Throttle ever sees it and there is nothing to recover.