Release notes

Developer Changelog

Public, developer-facing contract changes for the Throttle API, embedded checkout, webhooks, and SDKs. Internal refactors and UI-only changes are excluded.

2026-06-21 — Cart email capture + richer cart.abandoned payload

API

  • Cart customerEmail. POST /api/v1/carts and PATCH /api/v1/carts/{id} accept an optional customerEmail — lightweight email capture without a full customer record. The cart response now also returns customerEmail and the stored shippingAddress / billingAddress.

Webhooks

  • Enriched cart.abandoned. The payload now includes shippingAddress and billingAddress (as stored on the cart, or null), and customer now represents a guest captured via the cart’s customerEmail as { id: null, email, firstName: null } — so anonymous carts with a captured email are recoverable. All fields remain additive.

SDK

  • @usethrottle/cart: CreateCartInput / UpdateCartInput / Cart gain customerEmail. @usethrottle/webhook-types: CartAbandonedData gains the address fields and a nullable customer id.

2026-06-20 — useThrottleCheckout hook

SDK

  • @usethrottle/checkout-react. New useThrottleCheckout hook that orchestrates a storefront checkout over a cart session: totals and selectedMethod bound to the cart, one-call selectMethod, a status state machine, automatic stale-cart recovery (rebuild + retry on cart_not_open), and createSession returning the checkoutSessionId for <PaymentEmbed>. See Cart sessions.

2026-06-20 — allowedMethods on payment-only embeds

API

  • Fail-loud. POST /api/v1/checkout-sessions/embed-token (payment-only) now rejects allowedMethods with 400 allowed_methods_unsupported instead of accepting and silently ignoring it. A payment-only Gr4vy embed renders the methods configured on your Gr4vy connection; the embed token has no method-restriction field. allowedMethods continues to filter the full hosted checkout (the /payment-methods catalog + payment tiles) — that flow is unchanged.

SDK

  • @usethrottle/checkout-sdk. createEmbedToken no longer accepts allowedMethods (it never applied to the payment-only embed). createSession still accepts it for the full checkout.

Docs

  • Documented the precedence between session allowedMethods and Gr4vy connection configuration. See Embedded Checkout.

2026-06-20 — Cancel a checkout session

API

  • DELETE /api/v1/checkout/sessions/{id} now cancels an in-flight session (previously a no-op). It is idempotent, marks the session cancelled, and re-opens an associated cart still in checkout status (never a terminal converted cart). A completed session returns 422 already_completed; an unknown session returns 404.

SDK

  • @usethrottle/checkout-sdk. New checkout.cancelSession(sessionId) method.

Docs

  • Clarified the session→cart lifecycle: creating a session does not move the cart out of open; the cart only becomes converted when the order is created at session completion. See Embedded Checkout.

2026-06-20 — Canonical cart address + typed cart errors

API

  • Canonical cart address. PATCH /api/v1/carts/{id} now validates shippingAddress / billingAddress at write time against one canonical shape (CartAddress: required addressLine1, city, countryCode). Non-canonical keys (line1, state, country, zip) are now rejected with a validation_error naming the camelCase replacement, instead of being stored verbatim and failing later at checkout with address_required.

SDK

  • @usethrottle/cart. Exports the canonical CartAddress type (used by carts.update and the cart response) and two typed lifecycle errors — CartNotOpenError (409 cart_not_open) and CartNotFoundError (404). Both extend ThrottleApiError, so existing checks keep working. See Errors.

Docs

  • Clarified that selecting a shipping method is a single atomic call returning the full recomputed cart, and that the cart (not a client-side copy) is the source of truth for the selected method and totals. See Cart API.

2026-06-20 — Abandoned-carts read APIs

API

  • New endpoints. GET /api/v1/abandoned-carts (cursor-paginated; each row carries customer, total, itemCount, abandonedAt, and recoveryStatus) and GET /api/v1/abandoned-carts/summary (abandonedCount, abandonedValue, recoveryEmailsSent over a trailing window). Both require the carts:read scope. Available in @usethrottle/api-client. See API reference.

2026-06-20 — Richer cart.abandoned webhook payload

Webhooks

  • Enriched payload. The cart.abandoned outbound event now carries the full recovery context in data: customer (id, email, firstName; or null for anonymous carts), lineItems, currency, totals (subtotal, taxTotal, shippingTotal, discountTotal, total), itemCount, and a recoveryUrl. This lets ESP integrations (e.g. Klaviyo) drive a recovery flow from the single webhook with no follow-up API call. See Webhooks.
  • Backward compatible. The change is purely additive — only cartId and sequence are guaranteed, so existing consumers are unaffected. The envelope version stays "1".
  • Typed. @usethrottle/webhook-types now types the enriched CartAbandonedData (new fields are optional). recoveryUrl is populated from the app's cartRecoveryUrlTemplate when set, otherwise null.

Embed config

  • Per-app abandonment threshold. PUT /api/v1/embed-config now accepts cartAbandonmentThresholdMinutes (and GET returns it): the minutes of inactivity before an open/checkout cart is treated as abandoned by the sweep. Range 15129600 (90 days). Pass null to clear; when unset, the platform default of 1440 (24h) applies. The value is per application and per environment. See API reference.

2026-05-11 — Team management & per-app roles

Workspace invitations

  • Two-tier role model. Workspaces now carry three roles: owner, workspace_admin, and member. Members get explicit per-application roles from admin, developer, finance, or viewer. See Team management and Permissions.
  • New invitations + members endpoints. POST /api/v1/workspaces/:workspaceId/invites, .../invites/:id/resend, .../invites/:id/revoke, GET .../members, GET .../members/me, PATCH .../members/:memberId, DELETE .../members/:memberId, DELETE .../members/:memberId/applications/:applicationId.
  • Strict email match on accept. POST /api/v1/invites/accept now requires the caller's Clerk verified primary email to match the invite token's email claim. Mismatch returns 403 invite/email_mismatch.
  • Permission introspection. GET /api/v1/auth/permissions returns the caller's effective workspaceRole and appRole plus the full static catalog. Use it to drive UI gating.
  • Auth context fields. Server-side handlers now see auth.workspaceRole, auth.appRole, and auth.workspaceMemberId on Clerk-authenticated requests. Legacy auth.role field preserved.

Emails

  • Four new templates seeded by @platform/emails: system.team_invite_resent, system.team_invite_accepted, system.team_access_revoked, system.team_role_changed.

Legacy compatibility

  • POST /api/v1/merchants/me/invites and its /resend, /revoke siblings continue to work — they delegate to the new team-service. Legacy clients sending { email, role: 'admin' } still receive role: 'admin' in the response envelope.

2026-05-04 — Collect flags, billing, metadata propagation

Embedded checkout

  • Collect flags shipped. POST /api/v1/checkout/sessions accepts a new collect: { shippingAddress: boolean; billingAddress: boolean } object on the request body. Defaults match historical behaviour (shippingAddress: true, billingAddress: false). See Collection Flags.
  • Billing address is now first-class. POST /api/v1/checkout-sessions/:id/complete accepts new billingAddress (same shape as shippingAddress) and billingSameAsShipping: boolean (default false). Required when collect.billingAddress is true on the session.
  • step: 'billing' postMessage event. The unified /s flow now emits an additional throttle.step.changed event with step: 'billing' when the buyer reaches the billing form.
  • field extras on address_required 422 responses. Validation errors at /complete now include a field key (e.g. "billingAddress" or "shippingAddress") so the iframe and API integrators can route the error to the right form section.
  • Pay button auto-disables until collect-flagged fields are complete. Parent-set submitDisabled still composes additively — see Parent Controls.
  • ?mode=payment-only deprecated. Still respected client-side for one release. New integrators should set collect: { shippingAddress: false, billingAddress: false } instead.

Discounts

  • Session-create discountCode. POST /api/v1/checkout/sessions accepts a new discountCode: string field. Validated synchronously; invalid codes return 422 discount_invalid. See Discounts.

Metadata + webhooks

  • Session metadata propagates to orders. User-attached metadata on a checkout session is merged into the order at conversion with precedence cart < session < {customerEmail}.
  • Reserved keys are stripped server-side. recurring, customer_prefill, mode, amount, currency, and externalCartId are removed from session metadata before persistence; use top-level request fields instead.
  • Session metadata caps. 50 keys / 10KB serialized. Over-size payloads return 422 metadata_too_large.
  • Webhook payloads now carry user metadata. order.created, payment.captured, and subscription.created include the merged data.metadata bag. See Metadata.