TBBNMerchantDocs
API

Trade session states

The most commonly misread part of this flow

Seller B does not wait for Seller A to pay, and vice versa — and this extends all the way through fulfillment, not just checkout. Each trader checks out with the store of the items they receive, so a trade has two checkouts: side A's (at A's store, paid by B's trader) and side B's (at B's store, paid by A's trader). As soon as a trade session reaches RESERVED, both checkouts are open, to be paid in either order, independently. As soon as one is paid, that store ships those items straight away. Don't build any code path that gates one checkout or fulfillment on the other's status.

Diagram

DRAFT → PENDING → OFFERED → ACCEPTED → RESERVING → RESERVED
                                                       |
                                          +------------+------------+
                                          v                         v
                                 WAITING_CHECKOUT_A       WAITING_CHECKOUT_B   ← parallel, independent
                                          |                         |
                                          v                         v
                                CHECKOUT_A_COMPLETE      CHECKOUT_B_COMPLETE
                                          |                         |
                        fulfillment starts/completes independently per side
                                          |                         |
                                          +------------+------------+
                                                       v
                                          both sides report fulfillment complete
                                                       |
                                                       v
                                                  COMPLETED             ← automatic, no manual step

Failure / side states, reachable from most steps above: CANCELLED, EXPIRED, PAYMENT_FAILED, RESERVATION_FAILED, FULFILLMENT_FAILED, RETURNED, REFUNDED, CLOSED_UNRECIPROCATED (see the holding policy below).

The holding policy

Two timers govern a merchant-checkout trade session, and only one is ever active at a time:

  1. Pre-payment hold — 7 days, from RESERVED. If neither side has completed checkout within 7 days, the whole session is cancelled automatically. No consequence to either party — nothing was ever paid, nothing to unwind.

  2. Counterparty hold — 14 days by default, from the first checkout paid; a merchant can set anything from 7 to 14 days (counterpartyHoldDays on PATCH /v1/merchants/{id}). The moment ONE checkout is paid, that store ships to the trader who paid, unaffected by anything below — the hold is a grace period for the other checkout, still unpaid. Its length is fixed when the hold starts, from the setting of the store whose checkout is still unpaid, so changing the setting later only affects holds that start afterward. Meanwhile:

    • The trader who hasn't paid gets a weekly reminder email showing how long they have left.
    • They may still pay at any point — that store then ships, and the hold clears.
    • Neither trader may simply cancel anymore — once one checkout is paid, closing the trade requires waiting out the hold.
    • After the hold elapses, either trader may close the trade via POST /v1/trade-sessions/:id/close-unreciprocated, with a required note — this releases the unpaid checkout's items back to their seller; the paid checkout stands and is never touched.
    • The store whose checkout is unpaid can do the same once the hold elapses, via POST /v1/trade-sessions/:id/excuse-fulfillment.
    • Some merchants opt into closing this automatically once the hold elapses.

Traders see and explicitly agree to the trade quote — what each of them pays, at which store — before an offer is sent or accepted, and to the applicable hold terms — the fixed 7-day window plus the real counterparty-hold length for their specific trade — before completing checkout.

A side whose merchant runs matching only settles on that merchant's own platform and has 14 days to report the outcome instead — a trade with such a side isn't subject to the 7-day pre-payment hold, and that side gets no counterparty hold of its own. See the matching-only lifecycle.

This whole policy applies only to merchant-checkout trades. A peer-to-peer trade (both sides individual, no merchant) has no checkout/payment concept at all — see "Peer-to-peer path" below.

Transition table

FromToTriggerSide effect
—DRAFTSeller starts composing an offernone external
DRAFTPENDINGOffer submittedoffer.created event
PENDINGOFFEREDOffer delivered/visible to the target selleroffer.created webhook
OFFEREDACCEPTEDTarget seller acceptsoffer.accepted event + webhook; trade session created
OFFEREDEXPIREDDefault timeout with no responseexpiry event
ACCEPTEDRESERVINGBoth sides' items begin lockingreservation.created events
RESERVINGRESERVEDAll listings on both sides lockedreservation.created webhook per side
RESERVINGRESERVATION_FAILEDAny listing became unavailable mid-locksession cancelled, no charges attempted
RESERVEDWAITING_CHECKOUT_A / WAITING_CHECKOUT_BImmediately, both in parallelcheckout.started webhook to each side's store
RESERVED/WAITING_*CANCELLEDPre-payment hold expires unpaid, or either party cancelsreservations released; no consequence to anyone
WAITING_CHECKOUT_ACHECKOUT_A_COMPLETEA's store reports payment.completed for side AA's store ships to B's trader immediately
WAITING_CHECKOUT_BCHECKOUT_B_COMPLETEMerchant reports payment.completed for side Bmirror of the row above
WAITING_CHECKOUT_*PAYMENT_FAILEDMerchant reports payment.failed, or default timeoutrecoverable failure state, not silently retried
(per side, independent)fulfillment started/completedThat side's own fulfillment webhooksfulfillment.started/fulfillment.completed, carrying side
both sides fulfilledCOMPLETEDBoth sides report fulfillment completeterminal state, automatic
counterparty hold openCLOSED_UNRECIPROCATEDEither trader or the unpaid store closes, or auto-closesonly the unpaid checkout's items released; session can never be resumed
post-COMPLETEDRETURNED / REFUNDEDMerchant-reportedinformational only

Multi-item trades

A session's A side and B side are each an array of listings, not a single listing — many-to-many is supported from ACCEPTED onward. A side is not RESERVED until every listing in its set is locked; a single unavailable listing fails the whole side's reservation, not a partial trade.

Peer-to-peer path

A peer-to-peer trade (every listing on both sides has no merchant) never uses WAITING_CHECKOUT_* at all. It goes RESERVED → a scheduling state directly, then either party confirms via POST /v1/trade-sessions/:id/scheduling/confirm or reports a failed exchange via .../scheduling/fail. None of the holding-policy behavior above applies to this path.