Request/response contract for the Shopify Customer Account & Hydrogen storefront checkout integration. This page documents fields that were previously undocumented but already live in production — keep this in sync with your integration.
POST /api/v1/checkout
POSThttps://<your-hyperbill-host>/api/v1/checkout
Prices the cart live, runs a real stock check, and creates a Stripe PaymentIntent. Authenticated with a shared StorefrontServiceToken — send it as Authorization: Bearer <token>. Content type application/json.
Request body
Field
Type
Notes
cart
array
required
1–20 line objects — see Cart line shape below.
customer_email
string
required
The only field that gates checkout on its own. Used to resolve or create the buyer's Hyperbill account on payment success.
customer_country
string
required
2-letter ISO country code (e.g. GB). Used for VAT calculation.
customer_name
string
optional
Max 200 chars. Used for account creation and email personalisation only.
customer_phone
string
optional
Max 32 chars. Must include the country code (E.164 format) — e.g. +447700900123, not 07700900123. Without a phone, no activation/top-up SMS can be sent for that order (see sms_opt_in below, which is also required). Persists onto the buyer's Hyperbill account after their first order, so a later order/top-up that omits this field can still be reached if a number is already on file.
sms_opt_in
boolean
optional
Must be the JSON boolean true exactly — any other value (omitted, false, or a truthy string like "true") is treated as no consent. Gates every activation/top-up SMS send: a phone number alone is not sufficient. The buyer's most recent explicit answer (on any order) is what's honoured going forward — an explicit false on a later order revokes a prior opt-in. Simply omitting the field is never treated as an opt-out of consent already on file.
customer_id
string
optional
The buyer's Shopify Customer GID (gid://shopify/Customer/…). Never gates checkout on its own — supplements customer_email for durable identity matching if the buyer's Shopify email ever changes later.
Both customer_phone and sms_opt_in should be sent together, from whatever value is on file for the buyer at the moment checkout is created — not left blank if the checkout flow captures them later in the process. If either is missing when the order is created, no SMS goes out for that order, even if the buyer supplies a phone/consent afterward in the same session.
Cart line shape
Field
Type
Notes
webbing_sku
string
required
Must match a published StorefrontPlan.webbing_sku.
quantity
integer
required
1–10. Must be exactly 1 for a top-up line.
topup_iccid
string
optional
Marks this line as a top-up of an existing, already-owned device rather than a new purchase. Resolved against customer_email under either of two ownership shapes — see below. Any other ICCID (doesn't exist, or belongs to neither shape) returns the same 404 esim_not_found, never distinguishable.
Two ways topup_iccid can belong to customer_email: (1) customer_email resolves to the account that owns the device (a Consumer/Traveler's own device, or a Business Travel owner topping up anything in their fleet, assigned or not), or (2) the device is currently assigned to customer_email as a business team member (no separate Hyperbill account required for this path). An unassigned device only matches path (1) — a team member can never top up a device that isn't assigned to them. Once a top-up has been provisioned onto a device, later unassigning it does not retroactively invalidate that top-up.
Malformed cart, missing required field, or bad country code.
400
invalid_json
Request body isn't valid JSON.
400
mixed_cart_not_allowed
The cart mixes Business (Business Travel/Business Top-up) and Travel (Travel/Travel Top-up) plans in one checkout — check out each category separately.
403
business_purchase_not_allowed
The cart is Business-category, but customer_email already belongs to an existing Consumer/Traveler account — an existing account can never be upgraded to Business by a purchase.
404
unknown_sku
No published plan matches webbing_sku.
404
esim_not_found
A topup_iccid doesn't exist, or doesn't belong to customer_email.
409
out_of_stock
No unassigned stock available for a non-top-up line.
422
no_price
The matched plan has no price configured.
422
business_email_required
The cart is Business-category, but customer_email is on a known free/personal provider (Gmail, Hotmail, Yahoo, etc.) — Business plans require a business email domain.
The physical Webbing SIM equivalent of /api/v1/checkout above — same auth (Authorization: Bearer <token>), same application/json, same response shape. A separate endpoint and a separate cart — a checkout here may only contain physical SIM SKUs, never mixed with eSIM SKUs from the other endpoint.
No instant delivery. A physical SIM has to be picked, assigned, and posted by a human — there is no QR code and nothing is provisioned automatically. On payment success, the buyer gets a receipt email and our team is notified to process the order. There is no topup_iccid concept on this endpoint.
Request body
Field
Type
Notes
cart
array
required
1–20 line objects: { "webbing_sku": string, "quantity": integer (1–10) }. webbing_sku must match a published physical SIM plan.
customer_email
string
required
Same as /api/v1/checkout.
customer_country
string
required
2-letter ISO country code. Used for VAT calculation.
customer_name
string
optional
Max 200 chars. Falls back to shipping_address.name if omitted, or vice versa — one of the two is required.
customer_phone
string
optional
Max 32 chars. Not currently used for any SMS on this flow — kept for parity with /api/v1/checkout and future use.
customer_id
string
optional
Same as /api/v1/checkout — the buyer's Shopify Customer GID.
shipping_address
object
required
See Shipping address shape below.
Shipping address shape
Field
Type
Notes
name
string
optional
Recipient name. Falls back to customer_name if omitted — one of the two is required.
line1
string
required
Max 200 chars.
line2
string
optional
Max 200 chars.
city
string
required
Max 100 chars.
region
string
optional
State/county/province, max 100 chars.
postal_code
string
required
Max 20 chars.
country
string
required
2-letter ISO country code.
phone
string
optional
Delivery contact number, may differ from customer_phone. Max 32 chars.
The IoT Contract checkout endpoint — same auth (Authorization: Bearer <token>), same application/json, same response shape as /api/v1/checkout above. A separate endpoint and a separate cart — a checkout here may only contain IoT Contract SKUs, never mixed with eSIM or physical Webbing SKUs from the other two endpoints.
What this is for. Every buyer here is a business (Reseller account, created automatically on payment — there is no consumer path for this catalog). Price already covers the one-time SIM cost plus the first month; every month after that is billed separately outside Shopify, once our team manually completes setup. On payment success the buyer gets an email receipt, a text/SMS receipt if they opted in, and a separate emailed PDF invoice — no QR code, no instant provisioning, nothing else comes back from this endpoint itself.
Billing and delivery address are both required, and are genuinely separate. They can differ (e.g. head office vs a branch site the hardware ships to) — don't default one from the other beyond what shipping_address.name itself already falls back to.
Request body
Field
Type
Notes
cart
array
required
1–20 line objects: { "sku": string, "quantity": integer (1–10) }. sku must match a published IoT Contract plan.
customer_email
string
required
Same as /api/v1/checkout.
customer_country
string
required
2-letter ISO country code. Used for VAT calculation.
customer_first_name
string
required
Max 100 chars. Sent separately, not as one combined name field — this flows straight through to our CRM as real first/last name data.
customer_last_name
string
required
Max 100 chars.
company_name
string
optional
Max 200 chars. Falls back to the buyer's name if omitted. Used for the Reseller account name and our CRM's Company record.
company_registration_number
string
optional
Max 64 chars.
vat_registration_number
string
optional
Max 64 chars.
title
string
optional
Max 200 chars. The buyer's own job title (not a salutation).
purchasing_contact
object
optional
{ "name": string, "email": string }, both optional within the object. May be a different person from the buyer.
invoice_contact
object
optional
Same shape as purchasing_contact.
head_user
object
optional
Same shape as purchasing_contact — the primary Simsonic user at the buyer's company.
customer_phone
string
optional
Max 32 chars. Also gates the SMS receipt — only sent if sms_opt_in is also true.
sms_opt_in
boolean
optional
Defaults to false (no SMS) if omitted — never inferred from a phone number being present alone.
customer_id
string
optional
Same as /api/v1/checkout — the buyer's Shopify Customer GID.
marketing_consent
object
optional
See Consent object shape below.
data_processing_consent
object
required
See Consent object shape below. We cannot lawfully process this order's personal data without it — checkout fails with data_processing_consent_required if it's missing, not agreed, or incomplete.
billing_address
object
required
See Billing address shape below.
shipping_address
object
required
See Shipping/delivery address shape below. Genuinely separate from billing_address, not an alias of it.
Billing address shape
Field
Type
Notes
line1
string
required
Max 200 chars.
line2
string
optional
Max 200 chars.
city
string
required
Max 100 chars.
region
string
optional
State/county/province, max 100 chars.
postal_code
string
required
Max 20 chars.
country
string
required
2-letter ISO country code.
Shipping/delivery address shape
Field
Type
Notes
name
string
optional
Recipient name. Falls back to customer_first_name + customer_last_name if omitted.
line1
string
required
Max 200 chars.
line2
string
optional
Max 200 chars.
city
string
required
Max 100 chars.
region
string
optional
State/county/province, max 100 chars.
postal_code
string
required
Max 20 chars.
country
string
required
2-letter ISO country code.
phone
string
optional
Delivery contact number, may differ from customer_phone. Max 32 chars.
Consent object shape
These are compliance records, not preferences (unlike sms_opt_in) — we record exactly what the buyer was shown and when, since we don't own or version your consent copy ourselves. Send the real wording your form displayed and the real moment they agreed, not a placeholder.
Field
Type
Notes
agreed
boolean
required
Must be true for data_processing_consent or checkout fails.
wording
string
required
The exact consent copy shown to the buyer at that moment.