Skip to main content

External Shipping Engine: Storefront API Integration Draft

warning

This document is a draft and what it contains is not available yet in any environment, and is subject to change.

This page covers the Storefront API surface for ESE integrations — querying shipping methods, selecting them, fetching pickup locations, and reading provider custom data. For the backend webhook contract, see the ESE protocol reference. For a reference of all new shipping fields and types introduced alongside ESE, see New Shipping Capabilities in Storefront API.

Old Methodology

info

If your integration queries checkout.widgets { ... on IngridWidget { ... } } for shipping/delivery options, you're using the old, widget-embed methodology:

selection {
checkout {
widgets {
kind
... on IngridWidget {
deliveryOptionsAvailable
ingridAttributes
reload
sessionId
}
}
}
}

In that model, the provider (e.g. Ingrid) renders its own delivery-options UI via sessionId/ingridAttributes, and Centra just relays session state to it. ESE replaces this: shipping options come back as normal ShippingMethod objects via checkout.shippingMethods, and your frontend renders its own UI from them — no embedded widget involved. Everything on this page describes the ESE methodology.

A hybrid integration — some selections on ESE, others on the Ingrid widget — is fine at the account level. It's not a per-request choice, though: querying widgets on a selection switches that selection to the old methodology permanently, for the rest of its lifetime — even if later requests on the same selection stop including widgets. There's no way back to ESE for that selection. Decide per selection, not per request, and don't query widgets on a selection you want to keep on ESE.


Get Shipping Methods

Key Principle

The frontend controls when Centra queries the ESE by choosing whether to include shippingMethods in its selection set. Background notifications happen automatically — the frontend does not control them and does not need to handle them.

shippingMethods is a field on CheckoutSelection, which is the type of the checkout field on Selection. When any Storefront API operation returns a Selection and the client has included checkout { shippingMethods } in its selection set, Centra queries the ESE synchronously before responding. If shippingMethods is not requested, Centra may still notify the ESE in the background but responds immediately without waiting for shipping options.

Pagination

labels, customerChoices, locations, shippingProviderCustomData, and their nested list fields (customerChoices[].options, shippingProviderCustomData[].data, openingHours.periods, openingHours.specialDays) all take (limit: Int = 20, page: Int = 1) arguments and return a { list, pagination } wrapper rather than a bare array. Select list { ... } to get the actual entries — the examples below do this throughout. pagination (a PaginationInfo) is optional to select if you don't need paging metadata.

The following fragment includes all available fields on ShippingMethod:

fragment SelectionWithShipping on Selection {
checkout {
shippingMethods {
id
name
selected
hasLocation
carrierName
serviceCode
provider
providerOptionId
internalOptionId
deliveryType
description
iconUrl
price {
value
currency { code }
formatted
}
originalPrice {
value
currency { code }
formatted
}
etd {
relative { min max units }
absolute { from to }
custom
}
labels {
list {
type
displayName
description
iconUrl
}
}
customerChoices {
list {
id
displayName
description
type
default
value
price { value currency { code } formatted }
options { list { key displayName description price { value currency { code } formatted } } }
}
}
selectedLocation {
id
displayName
address {
address1
city
zipCode
country { name }
}
latitude
longitude
distance { mode meters minutes }
openingHours {
periods { list { open { day hour minute } close { day hour minute } } }
specialDays { list { date { year month day } isClosed comment } }
}
openingHoursText
brickAndMortar
shippingProviderCustomData { list { provider data { list { key value } } } }
}
locations {
list {
id
displayName
address {
address1
city
zipCode
country { name }
}
latitude
longitude
distance { mode meters minutes }
openingHours {
periods { list { open { day hour minute } close { day hour minute } } }
specialDays { list { date { year month day } isClosed comment } }
}
openingHoursText
brickAndMortar
shippingProviderCustomData { list { provider data { list { key value } } } }
}
}
shippingProviderCustomData { list { provider data { list { key value } } } }
}
}
}

How Centra Calls the ESE

Centra calls the ESE in two distinct modes:

  • Notified: A fire-and-forget call Centra makes to the ESE backend whenever a session-level change occurs — for example, a shipping address update, a language change, or an item being added to the cart. No shipping options are returned. This happens transparently in the background; the frontend receives an instant response. Centra reserves the right to determine which session changes trigger a notification.

  • Queried: A blocking call Centra makes when the frontend explicitly requests shippingMethods on CheckoutSelection. This field is specific to the checkout flow. Centra waits for the ESE to respond before completing the API response (up to 10 seconds), and the returned shipping options are surfaced to the frontend.

Behaviour per Operation

OperationshippingMethods requestedESE notifiedESE queriedResponse
selection (query)NoNoNoInstant
selection (query)YesNoYesSlow (up to 10s)
setAddressNoYesNoInstant
setAddressYesYesYesSlow (up to 10s)
addItemNoYesNoInstant
addItemYesYesYesSlow (up to 10s)

The same pattern applies to all operations that modify session state — this includes voucher and gift card mutations, not just address and cart changes, since they can affect shipping price (e.g. a free-shipping voucher).

Caching & Staleness

The table above shows the worst case. Centra caches the ESE's last shippingMethods response for up to 5 minutes. Requesting shippingMethods only triggers a fresh, slow ESE query when that cache has expired or been invalidated — otherwise it returns the cached options instantly, even though the request is "Queried" in the table above.

The cache is invalidated (forcing the next shippingMethods request to re-query) whenever a notify-triggering operation succeeds — so an addItem or setAddress call between two shippingMethods requests means the second one is slow again, even if it's within the 5-minute window.

Querying widgets is a one-way switch

Querying widgets on a selection — at any point, even once — switches that selection to the old, widget-driven methodology for good. From then on shippingMethod (the currently-selected method) stops being populated by ESE for the rest of that selection's lifetime, even once you stop requesting widgets. shippingMethods (the list of available options) may keep reflecting ESE data, but there's no way to move a selection back to ESE once widgets has been queried on it — start a new selection instead. See Old Methodology above.

Filtering by Display Target

shippingMethods accepts an optional optimizeFor string argument. When provided, Centra forwards the value to the ESE so it can return a tailored set of shipping options for that specific display context — for example, a reduced set without customer choices or location pickers for express payment surfaces, or a context-specific set for a custom integration.

The value is an enum. Your ESE implementation can recognise these values and return an appropriate set of options for each. The full list of values is subject to change as new display targets are added.

ValueContext
"applepay"Apple Pay payment sheet
"googlepay"Google Pay payment sheet
"ams"AMS
query {
selection {
checkout {
shippingMethods(optimizeFor: "applepay") {
id
name
price { value currency { code } formatted }
}
}
}
}

When optimizeFor is omitted, the full standard set of shipping options is returned.

ShippingMethod Fields

FieldTypeDescription
idInt!Unique identifier for the shipping method
nameString!Human-readable name shown to the customer
priceMonetaryValue!The effective price of this shipping method
originalPriceMonetaryValuePre-discount price, if applicable — use to display a crossed-out original price
selectedBoolean!Whether this method is currently selected on the checkout
hasLocationBoolean!Whether this method supports selectable pickup/drop-off locations. Use this as the authoritative capability flag
locationsShippingMethodLocationList!Paginated. Pickup/drop-off locations pre-populated by the ESE for the current shipping address. May be empty even when hasLocation is true — when empty, call getShippingMethodLocations with the order address to retrieve locations
carrierNameStringName of the shipping carrier
serviceCodeStringCarrier service identifier
providerStringMachine-readable identifier of the ESE provider/plugin that returned this option (e.g. "ingrid"). Distinct from carrierName, which is the customer-facing carrier brand. null when the method does not originate from an external shipping provider
providerOptionIdStringThe provider's own identifier for this shipping option, passed through as-is. Not for customer display — mainly useful for correlating with the provider's own logs or support tooling. null when not supplied
internalOptionIdIntAn internal numeric identifier assigned by the ESE/provider system, passed through as-is. null when not supplied
deliveryTypeDeliveryTypeFulfillment method. One of: TO_DOOR, PICKUP, LOCKER, MAILBOX, OTHER. null when the method does not originate from an external shipping provider
descriptionStringAdditional details about the option
iconUrlStringURL to the carrier's icon
etdShippingETDEstimated time of delivery. Any combination of sub-fields can coexist. When custom is present, we recommend displaying it in place of any label derived from relative or absolute — Centra does not enforce this on the backend
labelsShippingLabelList!Paginated. Visual indicators attached to the option (e.g. sustainability badge, signature required)
customerChoicesShippingMethodCustomerChoiceList!Paginated. Customer-configurable selections for this option — INPUT (text entry), CHECKBOX (toggle), CHOICE (dropdown with sub-options), or TIMESLOT (delivery time window selection). See ShippingMethodCustomerChoice Fields
selectedLocationShippingMethodLocationThe pickup/drop-off location currently attached to this method, or null if none has been set or the method does not support locations
shippingProviderCustomDataShippingProviderCustomDataList!Paginated. Provider-defined key/value custom data attached to this shipping method by the ESE. Same structure as Session.shippingProviderCustomData

ShippingMethodCustomerChoice Fields

FieldTypeDescription
idString!Identifier for this choice. Pass this back as id in ShippingMethodCustomerChoiceValueInput when submitting a value
displayNameString!Label shown to the customer
descriptionStringExplanatory text
typeString!INPUT, CHECKBOX, CHOICE, or TIMESLOT
defaultStringDefault value before any submission. For INPUT: shown in the input box, defaults to empty. For CHOICE and TIMESLOT: the option with the matching key is pre-selected, defaults to the first option. For CHECKBOX: "1" means checked, defaults to unchecked
valueStringThe value most recently submitted via setShippingMethodCustomerChoices / setDeliveryGroupShippingMethodCustomerChoices. null until a value has been submitted for this choice
priceMonetaryValueAdditional cost on top of the base shipping price (additive, not a replacement). null when the choice has no extra cost
optionsShippingMethodCustomerChoiceOptionList!Paginated. Sub-options — present when type is CHOICE or TIMESLOT. Each option has key, displayName, description, and price

Advantages

  • No new API parameters or separate triggering calls needed — the ESE is queried purely based on what fields are selected.
  • Easy to reason about: if you want shipping methods, include them in your query; Centra does the rest.
  • Frontend controls the tradeoff between response speed and data completeness.

Considerations

  • Requesting shippingMethods will significantly slow the response (up to 10 seconds).
  • If the ESE is slow or unavailable, the entire mutation response is delayed. Design your loading states accordingly.
  • The frontend is responsible for choosing the right moment to trigger an ESE query.

Both patterns share the same starting point:

1. User adds item to cart — instant (ESE notified in background)

mutation {
addItem(item: { product: 1, quantity: 1 }) {
selection {
lines { quantity product { name } }
}
}
}

2. User arrives at checkout — instant, page renders

query {
selection {
checkout {
shippingAddress { city country }
}
}
}

From here, two patterns diverge depending on your UX requirements.

Pattern 1: Combined

Set the address and fetch shipping methods in a single call.

3. User sets address and fetches shipping methods — up to 10 seconds (ESE queried)

mutation {
setAddress(shippingAddress: { ... }) {
selection {
checkout {
shippingAddress { city country }
shippingMethods { id name selected hasLocation price { value currency { code } } }
}
}
}
}

Trade-off: Simpler orchestration, but the address update blocks for up to 10 seconds while the ESE responds.

Set the address instantly, then fetch shipping methods in a separate background call.

3. User sets address — instant (ESE notified in background)

mutation {
setAddress(shippingAddress: { ... }) {
selection {
checkout { shippingAddress { city country } }
}
}
}

4. Immediately fire: fetch shipping methods — up to 10 seconds (ESE queried)

query {
selection {
checkout {
shippingMethods { id name selected hasLocation price { value currency { code } } }
}
}
}

Trade-off: More orchestration on the frontend, but the address update is instant and the shipping selector appears once ready.

Shipping Methods per Delivery Group

When an order spans multiple delivery groups (e.g. items from different warehouses), each group can have its own set of available shipping methods. shippingMethods is a new field added to SelectionDeliveryGroup — the delivery group type within CheckoutSelection — and is not present on the base DeliveryGroup type used elsewhere (e.g. in order history).

query {
selection {
checkout {
deliveryGroups {
id
shippingMethods {
id
name
hasLocation
price { value currency { code } }
}
items {
quantity
product { name }
}
}
}
}
}

Including shippingMethods inside deliveryGroups triggers a single ESE query, the same as checkout.shippingMethods — the ESE response is what determines the options available per group.


Get Shipping Method Locations

Some shipping methods — such as parcel shop networks or click-and-collect points — require the customer to choose a physical pickup or drop-off location. These are fetched via getShippingMethodLocations, which maps to the ESE's optionLocation request.

Fetching Locations

warning

Only call getShippingMethodLocations when ShippingMethod.hasLocation is true — only render the location picker UI for methods that support it.

The ESE is not required to populate ShippingMethod.locations inline — locations may be empty even when hasLocation is true. When locations is empty, call getShippingMethodLocations with the order address as the search address; Centra handles the location lookup. If locations is already populated, you may not need to call it at all — but you can still use getShippingMethodLocations to search at a different address or retrieve more results.

getShippingMethodLocations accepts a shipping method ID, an optional deliveryGroupId to scope the lookup to a specific delivery group's own shipping methods, and an optional location hint. The hint can be an Address, geographic coordinates, or omitted entirely — in which case the selection's current shipping address is used.

# Using an address
query {
getShippingMethodLocations(
shippingMethodId: 42
deliveryGroupId: "1"
address: {
address1: "14 Downing Street"
city: "London"
zipCode: "SW1A 2AA"
country: "GB"
}
) {
id
displayName
address {
address1
city
zipCode
country { name }
}
openingHours {
periods { list { open { day hour minute } close { day hour minute } } }
specialDays { list { date { year month day } isClosed comment } }
}
}
}

# Using coordinates
query {
getShippingMethodLocations(
shippingMethodId: 42
deliveryGroupId: "1"
latitude: 51.5033
longitude: -0.1276
) {
id
displayName
address {
address1
city
zipCode
country { name }
}
openingHours {
periods { list { open { day hour minute } close { day hour minute } } }
specialDays { list { date { year month day } isClosed comment } }
}
}
}

# Defaulting to the selection's shipping address
query {
getShippingMethodLocations(
shippingMethodId: 42
deliveryGroupId: "1"
) {
id
displayName
address {
address1
city
zipCode
country { name }
}
openingHours {
periods { list { open { day hour minute } close { day hour minute } } }
specialDays { list { date { year month day } isClosed comment } }
}
}
}

Exactly one location hint strategy applies per call. Providing both address and coordinates is an error.

ShippingMethodLocation Fields

FieldTypeDescription
idString!Unique identifier for this location
displayNameString!Human-readable name of the location
addressAddressPhysical address of the location
latitudeFloatGeographic latitude of the location
longitudeFloatGeographic longitude of the location
distance[ShippingMethodLocationDistance]Distance entries from the shipping address to this location, one per travel mode. Each entry has mode (DRIVING or WALKING), meters (Int), and minutes (Int) — at least one of meters or minutes is present.
openingHoursOpeningHoursOpening hours, including regular periods and special days
openingHoursTextStringHuman-readable opening hours summary
brickAndMortarIntID of a Centra brick-and-mortar store that may fulfill the order
shippingProviderCustomDataShippingProviderCustomDataList!Paginated. Provider-defined key/value custom data attached to this location by the ESE. Same structure as Session.shippingProviderCustomData

Set Shipping Method

Storefront API reference

Pass back the id received from ShippingMethod.id in the shippingMethods response — no difference in behaviour whether the method comes from Centra or an ESE.

If the method supports locations (hasLocation: true), you can attach the customer's chosen location in the same call via the optional locationId parameter (using ShippingMethodLocation.id from getShippingMethodLocations). The selected location is then readable back on checkout.shippingMethod.selectedLocation.

warning

Use setShippingMethod only when the selection has no delivery groups, and setDeliveryGroupShippingMethod only when it does. Using the wrong mutation for the selection's structure will result in an error.

# Without a location
mutation {
setShippingMethod(id: 12) {
selection {
checkout { shippingMethod { id name } }
}
}
}

# With a location
mutation {
setShippingMethod(id: 12, locationId: "loc-7") {
selection {
checkout {
shippingMethod {
id
name
selectedLocation {
id
displayName
address { address1 city zipCode country { name } }
}
}
}
}
}
}

Setting a Shipping Method per Delivery Group

When the checkout has multiple delivery groups, each group may have its own set of available shipping methods. A dedicated setDeliveryGroupShippingMethod mutation handles this case. This mutation is new and specific to ESE delivery group checkouts.

# Without a location
mutation {
setDeliveryGroupShippingMethod(deliveryGroupId: "1", shippingMethodId: 12) {
selection {
checkout {
deliveryGroups {
id
shippingMethods { id name selected }
}
}
}
}
}

# With a location
mutation {
setDeliveryGroupShippingMethod(deliveryGroupId: "1", shippingMethodId: 12, locationId: "loc-7") {
selection {
checkout {
deliveryGroups {
id
shippingMethods {
id
name
selected
selectedLocation { id displayName }
}
}
}
}
}
}

Submit Shipping Method Customer Choices

Storefront API reference

Once a shipping method with customerChoices is selected, submit the customer's picks with setShippingMethodCustomerChoices (no delivery groups) or setDeliveryGroupShippingMethodCustomerChoices (with delivery groups) — the same split as setShippingMethod/setDeliveryGroupShippingMethod.

Each choice is submitted as a ShippingMethodCustomerChoiceValueInput:

FieldTypeDescription
idString!The ShippingMethodCustomerChoice.id being answered. Must belong to the currently selected shipping method's customerChoices
valueString!The submitted value. For INPUT: free text, max 80 characters. For CHOICE: must match one of that choice's options[].key. For CHECKBOX and TIMESLOT: any string is currently accepted

A choice ID that isn't present on the currently selected shipping method, or a value that fails its type's validation, returns a user error. Submissions are all-or-nothing — if any choice in the call fails validation, none of the choices in that call are applied.

warning

Use setShippingMethodCustomerChoices only when the selection has no delivery groups, and setDeliveryGroupShippingMethodCustomerChoices only when it does. Using the wrong mutation for the selection's structure will result in an error.

# Without delivery groups
mutation {
setShippingMethodCustomerChoices(choices: [{ id: "parcel-size", value: "l" }]) {
selection {
checkout {
shippingMethod {
id
customerChoices { list { id value } }
}
}
}
}
}
# With delivery groups
mutation {
setDeliveryGroupShippingMethodCustomerChoices(
deliveryGroupId: "1"
choices: [{ id: "gift-message", value: "Happy birthday!" }]
) {
selection {
checkout {
deliveryGroups {
id
shippingMethods {
id
customerChoices { list { id value } }
}
}
}
}
}
}

Session as Integration Anchor Point

Because shipping options are tied to the session, the session ID can serve as a stable anchor for frontend integrations with third-party shipping providers. For example, a provider like Ingrid can associate customer-specific delivery promises — such as "This item can be with you in 3 days" — directly with the Centra session, without requiring a separate authentication flow on the frontend.

Your frontend widget can contact the provider directly using the session token, and the provider's backend can correlate that with the corresponding Centra ESE session context.

Shipping Custom Data

The ESE may return shippingProviderCustomData in both NOTIFY and CHECKOUT responses. Centra attaches this custom data at three levels — Session, ShippingMethod, and ShippingMethodLocation — without the frontend needing to do anything extra. Keys and values are defined by the ESE provider — Centra passes them through without interpretation. Entries are grouped by provider, with each group containing a data array of key/value pairs.

query {
session {
shippingProviderCustomData { list { provider data { list { key value } } } }
}
}

query {
selection {
checkout {
shippingMethods {
shippingProviderCustomData { list { provider data { list { key value } } } }
locations {
list {
shippingProviderCustomData { list { provider data { list { key value } } } }
}
}
}
}
}
}

Nota Bene: Backend-Only Calls

The ESE protocol includes additional request types — testConnection and orderCreated — that Centra uses for backend-to-backend operations. These are handled entirely server-side and have no frontend surface. You do not need to account for them in your Storefront API integration.

note

Express checkout flows (e.g. Apple Pay) are fully supported. Centra handles the ESE integration transparently — no additional frontend implementation is required.