Headless Checkout API - Frontend Integration
The Headless Checkout API is a backend solution designed to enable the creation of a custom checkout experience, eliminating the need for direct frontend integration.
In this article, we offer tips on how to implement the checkout UI using the data provided in our responses.
Presenting delivery options
Here is part of the response from headless checkout:
{
"deliveries": [
{
"id": "1",
"delivery_categories": [
{
"id": "home-delivery-1384f5e83e3b4143a1c2475214a1de2c",
"delivery_type": "DELIVERY",
"display_name": "Home Delivery",
"delivery_options": [
{
"id": "0196f224-1037-7aff-a6aa-972b631188a2",
"carrier": {
"name": "InPost",
"product_id": "inpost-d2d"
}
}
]
}
]
}
]
}
Important data fields:
deliveries
- in most cases there is one delivery, in split checkout there will be multiple deliveries (one for each group of items)
deliveries[].delivery_categories
- (often referred to as category) each category is an abstraction of delivery choices for user, eg "Home Delivery", "Pickup Points", "Pick in Store"
deliveries[].delivery_categories[].delivery_options[]
- (often referred to as option) each option is an actual choice, eg
- category "Home Delivery" could have two options: one for PostNord and one for DHL so user can pick actual carrier
- category "Pickup Points" could have multiple options, one for each pickup point
- category "Pick in Store" could have multiple options, one for each store
We expose sort_order
for categories and options. Our api already sorts categories and options according to those values
but if different sort order is needed, you can use this field to get original order.
Home delivery
Depending on the need, you can show:
- all available carriers for home delivery
- just "Home Delivery" and automatically select carrier with custom logic
For mulitple carriers for home delivery, check option.carrier
- this is the carrier and carrier product that will deliver the package to the user, full list in Supported Carrier Products.
Useful display fields:
<category>.name
- display name for category<option>.carrier.name
- display name for carrier<option>.carrier.product_name
- display name for carrier product
{
"deliveries": [
{
"id": "1",
"delivery_categories": [
{
"id": "home-delivery-1384f5e83e3b4143a1c2475214a1de2c",
"delivery_type": "DELIVERY",
"display_name": "Home Delivery",
"delivery_options": [
{
"id": "0196f224-1037-7aff-a6aa-972b631188a2",
"carrier": {
"name": "InPost",
"product_name": "Kurier",
"product_id": "inpost-d2d"
},
"etd": {
"relative": {
"unit": "TIME_UNIT_BUSINESS_DAY",
"earliest": 4,
"latest": 5
}
},
"expire_time": "2025-05-25T22:00:00Z"
}
]
}
]
}
]
}
Pickup / Instore delivery
In most cases there will be one category for pickup points and each pickup point will have it's own option. Each pickup point typically is presented as a list of locations with a name and address on a map or on a list. User can pick only one pickup point.
It is possible to have one category with multi-carrier pickup point locations.
Check option.carrier
- for carrier and carrier product that will deliver the package to the pickup/instore location, full list in Supported Carrier Products.
Useful display fields:
<category>.name
- display name for category<option>.carrier.name
- display name for carrier<option>.carrier.product_name
- display name for carrier product<option>.pickup_location.title
- display name for location
{
"deliveries": [
{
"id": "1",
"delivery_categories": [
{
"id": "pickup-bc63c26bfdd4480d815a9d4c6f221094",
"delivery_type": "PICKUP",
"display_name": "Pickup Lockers",
"sort_order": 1,
"options_source": "CARRIER",
"delivery_options": [
{
"id": "0196f224-1036-7f2d-8bcf-0b3ac7267a63",
"carrier": {
"name": "DHL eCommerce Netherlands",
"product_name": "DHL Service Point",
"product_id": "dhl-svp",
"sort_order": 2
},
"pickup_location": {
"id": "PL-4513121",
"external_id": "PL-4513121",
"location_type": "STORE",
"title": "Żabka",
"visiting_contact": {
"address": {
"country_code": "PL",
"postal_code": "50-128",
"city": "Wrocław",
"address_lines": [
"Św. Mikołaja 16"
],
"coordinates": {
"lat": 51.111725,
"lng": 17.027769
}
}
},
"operational_hours": {
"monday": [
"06:00-23:00"
],
"tuesday": [
"06:00-23:00"
],
"wednesday": [
"06:00-23:00"
],
"thursday": [
"06:00-23:00"
],
"friday": [
"06:00-23:00"
],
"saturday": [
"06:00-23:00"
],
"sunday": [
"11:00-18:00"
]
},
"distances": {
"walking": {
"meters": "58",
"minutes": "1"
},
"driving": {
"meters": "414",
"minutes": "1"
}
}
},
"etd": {
"relative": {
"unit": "TIME_UNIT_BUSINESS_DAY",
"earliest": 6,
"latest": 8
}
},
"expire_time": "2025-05-25T22:00:00Z"
}
]
}
]
}
]
}
Selecting delivery options
For each deliveries
you need to keep track of deliveries[].delivery_categories[].delivery_options[].id
that user selected.
User can select exactly one option per delivery and all deliveries needs to have an option selected.
To help with initial choice you can use deliveries[].delivery_categories[].delivery_options[].preselected
to show selection when user opens the checkout page for the first time.
There will be only one preselected option per each delivery.
Other topics
Delivery time
Delivery time can be presented to the user based on etd
(estimated time of delivery) object available on option level.
{
"delivery_options": [
{
"id": "0196f224-1037-7aff-a6aa-972b631188a2",
"carrier": {
"name": "InPost",
"product_name": "Kurier",
"product_id": "inpost-d2d",
"external_id": "asdf"
},
"etd": {
"relative": {
"unit": "TIME_UNIT_BUSINESS_DAY",
"earliest": 4,
"latest": 5
}
},
"expire_time": "2025-05-25T22:00:00Z"
}
]
}
Please check swagger for all possible options in etd
object.
Example js:
const formatRelativeTime = (relative: { earliest: number, latest: number, unit: string }) => {
let unitText = '';
const isPlural = relative.latest !== 1;
switch(relative.unit) {
case 'TIME_UNIT_MINUTE':
unitText = isPlural ? 'minutes' : 'minute';
break;
case 'TIME_UNIT_HOUR':
unitText = isPlural ? 'hours' : 'hour';
break;
case 'TIME_UNIT_DAY':
unitText = isPlural ? 'days' : 'day';
break;
case 'TIME_UNIT_BUSINESS_DAY':
unitText = isPlural ? 'business days' : 'business day';
break;
case 'TIME_UNIT_WEEK':
unitText = isPlural ? 'weeks' : 'week';
break;
case 'TIME_UNIT_MONTH':
unitText = isPlural ? 'months' : 'month';
break;
default:
unitText = isPlural ? 'days' : 'day';
break;
}
if (relative.earliest === relative.latest) {
return <span>Delivered in {relative.earliest} { unitText }</span>
}
return <span>
Delivered in {relative.earliest}-{relative.latest} { unitText }
</span>
}
const formatAbsoluteTime = (absolute: { earliest_time: string, latest_time: string }) => {
// date: 2025-06-18T06:00:00Z
if (!absolute || !absolute.earliest_time || !absolute.latest_time) {
return <span className="text-gray-400">Invalid estimated time: { JSON.stringify(absolute) }</span>;
}
const earliestDate = new Date(absolute.earliest_time);
const latestDate = new Date(absolute.latest_time);
const options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
};
return <span>
Delivered between {earliestDate.toLocaleString('en-PL', options)} and {latestDate.toLocaleString('en-PL', options)}
</span>;
}
const formatEtd = (etd: ETD) => {
if (!etd) {
return <span className="text-gray-400">No estimated time</span>;
}
if (etd.custom !== undefined) {
return <span>{etd.custom.text}</span>;
}
if (etd.relative !== undefined) {
return formatRelativeTime(etd.relative);
}
if (etd.absolute !== undefined) {
return formatAbsoluteTime(etd.absolute);
}
return <span className="text-gray-400">No estimated time</span>;
}
Relative delivery time
Here are some examples of how to present relative delivery time to the user:
Earliest | Latest | Unit |
Ingrid Widget Example |
---|---|---|---|
1 | 2 | TIME_UNIT_DAY |
Delivered in 1-2 days |
1 | 2 | TIME_UNIT_BUSINESS_DAY |
Delivered in 1-2 business days |
141 | 151 | TIME_UNIT_HOUR |
Delivered in 141–151 hours |
5123 | 5124 | TIME_UNIT_MINUTE |
Delivered in 5123-5124 minutes |
1 | 1 | TIME_UNIT_WEEK |
Delivered next week |
1 | 1 | TIME_UNIT_MONTH |
Delivered in 1 month |
Absolute delivery time
Here are some examples of how to present absolute delivery time to the user:
Earliest |
Latest |
Ingrid Widget Example |
---|---|---|
2025-05-22T08:29:32Z | 2025-05-22T08:29:32Z | Delivered on 2025-05-22 |
2025-05-22T08:29:32Z | 2025-05-22T08:29:32Z | Delivered next day |
2025-05-22T08:29:32Z | 2025-05-23T08:29:32Z | Delivered in 1-2 days |
2025-05-22T08:29:32Z | 2025-05-23T08:29:32Z | Delivered in 1-2 business days |
Warehouse
There's option to present warehouse information to the user. It is available in category.
{
"deliveries": [
{
"id": "1",
"delivery_categories": [
{
"id": "home-delivery-1384f5e83e3b4143a1c2475214a1de2c",
"delivery_type": "DELIVERY",
"warehouse": {
"id": "swe-62aac37054cb4b60862e7aebc33b38ab",
"address": {
"country_code": "SE",
"postal_code": "111 34",
"city": "Stockholm",
"address_lines": [
""
]
}
}
}
]
}
]
}
Addons
Addons are selectable extra paid services that can be added to the delivery. Example: "gift wrapped", "extra fast".
Addons are available on category level and can be selected by the user.
{
"deliveries": [
{
"id": "1",
"delivery_categories": [
{
"id": "delivery-a20772d216ff43aa9b45182e6fcfbdc1",
"delivery_type": "DELIVERY",
"display_name": "Home Delivery",
"delivery_options": [],
"addons": [
{
"display_name": "Express delivery",
"price": "500",
"addon_type": "CUSTOM",
"id": "19051ba0-682b-11ee-ae2a-9ee5ca455892"
}
]
}
]
}
]
}
If user selects addon, addon id needs to be sent to backend in complete session request.