Toast (Partner Integration)

Status: connector shipping in SP23. This guide is written from the implementation plan’s pinned contract (docs/superpowers/specs/2026-07-20-sp23-toast-connector-design.md); its wording is checked against the as-shipped code at SP23’s final review, so treat every claim here as the committed design, not a promise about what has merged in any given moment.

Toast is architecturally different from every other connector on this platform. Square, Clover, Lightspeed’s order webhooks, LithosPOS, and the generic connector all push order data at this platform and let it derive loyalty effects. Toast’s order payloads carry no loyalty identifier at all — the only place a Toast register ever mentions a guest’s loyalty identity is a single, partner-hosted Loyalty Integration API endpoint that Toast calls, synchronously, at specific points in a check’s lifecycle. So Toast inverts, the same way Lightspeed does, but richer: one endpoint, seven transaction types, a hard 5-second response budget.

The three surfaces

Surface Direction Role
The loyalty endpoint Toast calls us, synchronously, inside its 5s budget member search, balance/offers, signup, redemption commit, accrual, reversal — the only member-linked path
Connection lifecycle dashboard → us; us → Toast (auth, location listing) machine-client credentials, restaurantGuid/managementGroupGuid mapping, the partners webhook, token refresh
Orders reconciliation (poll) us → Toast, on a schedule a gap-fill safety net over Toast’s own Orders API — catches accruals whose loyalty call never arrived, ingested anonymously (see below)

The register flow

The core lifecycle a Toast check walks through, in order:

  1. Search — the cashier looks a guest up (phone, email, or a scanned loyalty identifier). Toast sends LOYALTY_SEARCH; the response is zero or more matched accounts, never a guess.
  2. Inquire — once a guest is attached to the check, Toast asks for their balance and any offers they can currently redeem (LOYALTY_INQUIRE).
  3. Redeem — the guest applies a reward at checkout (LOYALTY_REDEEM); this commits against the ledger in the same request — there is no “maybe it applied” state.
  4. Accrue — the check closes and payment completes (LOYALTY_ACCRUE); points are earned through the same pipeline every other connector’s transactions flow through.

Two more types exist outside that everyday flow:

Toast-Transaction-Type Fires when What this platform does
LOYALTY_SEARCH Cashier looks up a guest Member-matching chain over the search criteria Toast sends (phone, email, or a loyalty identifier tried as loyalty number then QR) → accounts[], possibly empty. Never a partial guess.
LOYALTY_INQUIRE A guest is attached to the check Current balance + offers[] — this guest’s currently-affordable rewards, autoApply always false in v1 (the register applies it, not us).
LOYALTY_SIGNUP Kiosk/guest-display enrollment Creates the member + phone/email identifier(s) + utility-only consent rows (never an inferred marketing opt-in, CLAUDE.md §9) → the new loyaltyIdentifier. A guest who already exists → ERROR_ALREADY_REGISTERED.
LOYALTY_REDEEM Guest applies a reward The existing two-phase quote→commit domain, both phases in this one request → ACCEPT with appliedRedemptions[] or rejectedRedemptions[] plus an honest userMessage (insufficient points, reward not applicable). Never partial-silent.
LOYALTY_ACCRUE Check closes, payment completes ACCEPT immediately; the transaction is persisted and enqueued into the same pipeline every connector uses — points land asynchronously, correct by the guest’s next LOYALTY_INQUIRE.
LOYALTY_REVERSE A prior accrual or redemption is undone Accrual reversals ride the existing proportional-refund machinery. Redemption reversal is not supported in v1 — an honest ERROR_TRANSACTION_CANNOT_BE_REVERSED-class response, ASSUMPTION(toast-cert)-tagged (see below).
LOYALTY_TRANSFER A guest tries to move points to another guest Honest, permanent-in-v1 unsupported error (ERROR_INVALID_TRANSFER) — there is no code path that moves points between two guests, or between two tenants (the same posture as coalitions’ groundwork).

What’s built vs. what’s user-gated

Built + stub-proven this SP — real code, exercised against Toast’s public OpenAPI spec (toast-integrations-loyalty-api.yaml) turned into fixtures, and wiremock stand-ins for Toast’s own APIs (auth login, the JWT public-key endpoint, partner restaurant listing, orders-bulk) — never against Toast’s real, live systems:

User-gated — cannot run against a real Toast restaurant without it, and nothing here is faked in the meantime:

Until credentials exist, the dashboard’s Toast card still renders honestly: the connect form is real, but submitting it reaches the same POST /v1/pos-connections route every other manually-created provider uses, and — like every provider other than LithosPOS — that route 400s until its Toast branch ships (see Connect a restaurant below). Nothing about the UI pretends a connection exists that doesn’t.

The partner application path

Toast’s own gate, not this platform’s — every stage below is Toast’s process, run once per partner (not per merchant):

Stage What happens
Application Apply to Toast’s partner program.
Agreement Contractual terms with Toast.
Dev Kickoff Toast issues sandbox credentials and a sandbox host.
Certification Toast issues production credentials after a live review (their own ~1-hour demo review is, in effect, Toast’s own end-to-end test of this integration).
Alpha → Beta → GA Staged rollout to real merchants.

Connect a restaurant

POST /v1/pos-connections

Auth: dashboard session or apiKey with connectors:manage — the same route and permission every other connection type uses.

Unlike Square, Clover, and Lightspeed (an OAuth redirect: GET /v1/pos/<provider>/connect) or LithosPOS (one optional label field), Toast is credentials-paste: the merchant (or this platform’s own operator, pre-certification) pastes the machine-client credentials Toast issued. The dashboard’s Integrations → Toast card collects:

Field Notes
Client ID Write-only — never echoed back after saving. PosConnectionResponse has no credentials field to echo it into, even by accident.
Client secret Write-only, same as above.
Restaurant GUID Required; this becomes the connection’s provider_account_ref — the routing key Toast-Restaurant-External-ID resolves against on every loyalty-endpoint call.
Sandbox host override Optional. Honest gated copy, not a guessed value: “Leave empty for production. Toast issues sandbox credentials after partner approval.” Toast doesn’t publish a sandbox hostname format before Dev Kickoff, so this field carries no example/placeholder pretending to know one — production’s confirmed default is ws-api.toasttab.com.

The intended shape (connect lifecycle work, not yet landed as of this guide — see the status note at the top): credentials are validated with a real POST /authentication/v1/authentication/login call, encrypted at rest, and never appear in any GET/response again — the identical “credentials never in any response” rule every other connection type already follows. Location metadata (restaurant name, management group) is expected to populate the connection’s mapping once Toast’s partner-restaurant listing is synced; until then, the dashboard shows exactly what it already knows — the restaurant GUID — and an honest “—” for anything it doesn’t, never a fabricated value.

Idempotency: two layers, different keys

Toast’s own retry semantics differ by transaction typeREDEEM is never retried, REVERSE retries reuse the same Toast-Transaction-GUID, and ACCRUE retries arrive with a different one. One idempotency key can’t cover all three, so this connector runs two, deliberately:

  1. Transport replay — the existing edge-api idempotency response store, keyed toast:<Toast-Transaction-GUID>. A retried request with the same GUID (Toast’s documented behavior for REVERSE) gets the byte-identical cached response back, stored 48h — Toast’s own requirement (“must return cached duplicate response”).
  2. Business/ledger dedupecanonical_transactions.external_id = toast-order-<orderGuid>-<checkGuid>. This is what actually protects ACCRUE: since a retried accrual carries a new GUID, the transport layer never sees it as a duplicate, but the order/check pair is stable across the retry — and across this connector’s own orders-poll gap-fill (both paths converge on the identical external id) — so the ledger’s own UNIQUE constraint makes double-earning structurally impossible, not just unlikely.

webhook_events delivery ids for the pipeline entries this endpoint creates follow the same order/check pairing — accrue:<orderGuid>:<checkGuid> / reverse:<orderGuid>:<checkGuid> — so even a different-GUID ACCRUE retry is caught a second time, at insert, before it ever reaches the queue.

REDEEM skips both layers by design — Toast never retries it — and instead commits synchronously, in-request, keyed toast-redeem-<orderGuid>-<checkGuid>-<rewardId>, through the same quote→commit domain the till API uses.

Latency posture

Toast’s own budget: 500ms average, 5 seconds hard — past 5s, Toast may close the connection and resend. This connector holds to that by keeping every code path free of external fetches: the JWT verification key is KV-cached (GET /usermgmt/v1/oauth/token_key, refetched once on a verification failure to handle key rotation, not on every request), and LOYALTY_ACCRUE deliberately does not wait for points to actually post — it accepts immediately and lets the same asynchronous pipeline every other connector uses earn the points, correct by the guest’s next LOYALTY_INQUIRE. LOYALTY_REDEEM is the one type that must do real work before responding (a ledger commit) and is budgeted as the heaviest of the seven.

The anonymous orders-reconciliation note

Toast’s Orders API (GET /orders/v2/orders/{guid}, ordersBulk) is a completely separate, loyalty-blind surface — order payloads never carry a loyalty identifier, full stop. That’s why the loyalty endpoint above has to be the primary earn path in the first place, and it’s also why this connector’s poll leg can only ever be a reconciliation safety net, not a source of member-linked earning:

Assumptions pending certification

Toast’s certification review (the partner path’s final stage) is the first point this platform can observe real traffic from a live register. Until then, these items are ASSUMPTION(toast-cert) in the code — implemented against the best available public documentation, fixture-pinned, and flagged for re-verification rather than silently treated as settled:

API reference

The loyalty endpoint (POST /webhooks/toast/loyalty) and the partners webhook (POST /webhooks/toast) are infrastructure endpoints, deliberately absent from the public OpenAPI spec — the same treatment every inverted or provider-signed callback on this platform gets (Square’s and Clover’s own OAuth callbacks, Lightspeed’s inverted endpoints). Both are covered by the existing /webhooks/* production route; nothing new to open at the edge. Their wire contract is this page.

POST /v1/pos-connections and its siblings (list, get, patch the location mapping, disconnect) are public — full request/response schemas are in the API reference under the PosConnections tag.

v1 limits

Next steps