Collection Flags
Tell Throttle which buyer addresses to collect during the embedded flow. Each session opts into shipping and billing capture independently — useful for digital-only carts, B2B Net 30 invoicing, and storefronts that already collected the address upstream.
The collect block
POST /api/v1/checkout/sessions accepts an optional collect object on the request body:
| Field | Type | Default | Behaviour |
|---|---|---|---|
collect.shippingAddress | boolean | true | When true, the iframe renders the shipping address step and /complete requires shippingAddress. |
collect.billingAddress | boolean | false | When true, the iframe emits a step: 'billing' postMessage and /complete requires billingAddress (or billingSameAsShipping: true). |
collect is equivalent to { shippingAddress: true, billingAddress: false }. Existing integrations that don't opt in keep the old shape.Worked combinations
Pick the combo that matches your storefront. Every example below is the request body for POST /api/v1/checkout/sessions.
Shipping only (default — physical goods, no separate billing)
curl -X POST https://api.usethrottle.dev/api/v1/checkout/sessions \
-H "X-API-Key: sk_production_xxx" \
-H "Content-Type: application/json" \
-d '{
"applicationId": "e7efb0a6-892e-46b2-97ab-296bb04c5b29",
"externalCartId": "cart_abc",
"amount": 2999,
"currency": "USD",
"collect": {
"shippingAddress": true,
"billingAddress": false
}
}'Billing only (digital goods + invoicing)
Common for B2B Net 30 flows where shipping is irrelevant but the AR system needs a billing address.
curl -X POST https://api.usethrottle.dev/api/v1/checkout/sessions \
-H "X-API-Key: sk_production_xxx" \
-H "Content-Type: application/json" \
-d '{
"applicationId": "e7efb0a6-892e-46b2-97ab-296bb04c5b29",
"externalCartId": "cart_abc",
"amount": 2999,
"currency": "USD",
"collect": {
"shippingAddress": false,
"billingAddress": true
}
}'Both (shipping + separate billing)
The buyer fills shipping, then is asked whether billing matches. If they uncheck "billing same as shipping", a billing form appears.
curl -X POST https://api.usethrottle.dev/api/v1/checkout/sessions \
-H "X-API-Key: sk_production_xxx" \
-H "Content-Type: application/json" \
-d '{
"applicationId": "e7efb0a6-892e-46b2-97ab-296bb04c5b29",
"externalCartId": "cart_abc",
"amount": 2999,
"currency": "USD",
"collect": {
"shippingAddress": true,
"billingAddress": true
}
}'Neither (replaces ?mode=payment-only)
Use when your storefront has already collected the buyer's addresses and stamped them on metadata.customer_prefill.
curl -X POST https://api.usethrottle.dev/api/v1/checkout/sessions \
-H "X-API-Key: sk_production_xxx" \
-H "Content-Type: application/json" \
-d '{
"applicationId": "e7efb0a6-892e-46b2-97ab-296bb04c5b29",
"externalCartId": "cart_abc",
"amount": 2999,
"currency": "USD",
"collect": {
"shippingAddress": false,
"billingAddress": false
}
}'Flow: which steps render
The two boolean flags fan out into four distinct iframe step machines. Use this to confirm the buyer journey before you ship — especially when a connector requires a billing address regardless of shipping.
flowchart LR
Start([Session created]) --> S{collect.shippingAddress?}
S -- true --> AddrShip[address step]
S -- false --> Bcheck1{collect.billingAddress?}
AddrShip --> Bcheck2{collect.billingAddress?}
Bcheck2 -- true --> Bill[billing step]
Bcheck2 -- false --> Pay1[payment step]
Bcheck1 -- true --> BillOnly[billing step]
Bcheck1 -- false --> Pay2[payment step]
Bill --> Pay3[payment step]
BillOnly --> Pay4[payment step]
Pay1 --> Done([throttle.completed])
Pay2 --> Done
Pay3 --> Done
Pay4 --> DoneComplete-session billing fields
When collect.billingAddress is true, the iframe POSTs the billing details on /complete. Two new request-body fields:
billingAddress— same shape asshippingAddress:name,line1,line2?,city,region,postalCode,country.billingSameAsShipping— boolean, defaults tofalse. Whentrue, the server copies the shipping address into the order's billing slot and you can omitbillingAddress.
POST /api/v1/checkout-sessions/sess_xxx/complete
{
"paymentMethod": "card",
"shippingAddress": {
"name": "Jane Doe",
"line1": "1 Market St",
"city": "San Francisco",
"region": "CA",
"postalCode": "94103",
"country": "US"
},
"billingAddress": {
"name": "Jane Doe",
"line1": "1 Market St",
"city": "San Francisco",
"region": "CA",
"postalCode": "94103",
"country": "US"
},
"billingSameAsShipping": false
}POST /api/v1/checkout-sessions/sess_xxx/complete
{
"paymentMethod": "card",
"shippingAddress": { "...": "..." },
"billingSameAsShipping": true
}Validation errors
When a required address is missing, the server returns 422 address_required with a field extra so the iframe can highlight the right step.
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": {
"code": "address_required",
"message": "Billing address is required for this session.",
"field": "billingAddress"
}
}Iframe behaviour
- The Pay button auto-disables until every collect-flagged field is complete. Parent-set
submitDisabledstill composes additively — see Parent Controls. - With
collect.billingAddress: true, the iframe emits an additionalthrottle.step.changedevent withstep: 'billing'. - With
collect.shippingAddress: falseandcollect.billingAddress: false, the buyer lands directly on the payment step (this combo replaces the legacy?mode=payment-onlyURL flag).
Migration: ?mode=payment-only
?mode=payment-only URL query parameter is still honoured client-side for one release window to avoid breaking existing storefronts. New integrators should set collect: { shippingAddress: false, billingAddress: false } on session create instead — that path is server-authoritative, carries through to /complete validation, and is visible in webhook payloads.Next
- Embedded Checkout — full embed lifecycle and event reference.
- Parent Controls — parent-side
submitDisabledgate.