Checkout V2 (Delivery Checkout) is the evolution of V1 (SIW). This page covers what changes between them and the steps to move an existing integration across.
If you are still deciding which integration to use, start with Ingrid checkout API options: V1, V2, and Headless.
Why migrate to V2
V1 is legacy and no longer actively maintained. Your existing V1 integration continues to work and no retirement date has been announced — but V1 receives no new capabilities, so the gap between the two widens over time.
V2 unlocks what V1 cannot support:
Built-in address form — address collection, validation, autocomplete, and address book as part of the widget. No need to build or maintain your own address form
Delivery groups — support for split shipments and marketplace setups, with addresses and delivery options per group
Session pull — a dedicated endpoint that refreshes delivery promises without side effects on session state
Currency conversion — built-in support for cross-border currency handling
Ingrid AI features — predictive delivery times and profit-optimized delivery require V2
Return promise — return conditions shown in checkout and stored with the order, reused across tracking, returns, and post-purchase communication. Requires Ingrid Returns
Simplified session complete — when using the address form, there is no need to pass the customer object at completion
Richer event model — the
summary_changedevent provides address and billing data alongside pricing, reducing the need for additional API calls
Migrating also positions your integration for future Ingrid platform capabilities, which are built on the V2 architecture.
Key differences at a glance
Aspect | V1 (SIW) | V2 (Delivery Checkout) |
Status | Legacy — no new capabilities | Current |
API base path |
|
|
Address handling | No built-in form — you or your payment provider handle the address | Built-in address form with autocomplete, validation, address book |
Session retrieval |
|
|
Split shipments | Not supported — single cart only | Delivery groups — supports split shipments and marketplace |
Currency conversion | Not supported | Built in |
Customer data at create and update |
|
|
| Optional | Required on |
Cart item fields |
|
|
Response structure |
|
|
Address in response |
| Inside |
Frontend events |
|
|
Ingrid AI support | Not available | Predictive delivery times and profit-optimized delivery |
| Not available | Optional on |
Migration checklist
Step 1: Update the API endpoint
Change your base path from the SIW API to the Delivery Checkout API.
| V1 | V2 |
Base path |
|
|
Create |
|
|
Get |
|
|
Pull | — |
|
Update |
|
|
Complete |
|
|
Authentication stays the same — a bearer token in the Authorization header.
Step 2: Move customer data to session complete
In V1, you could pass the customer field on session.create and session.update. In V2, customer is only accepted on session.complete.
What to change:
Remove
customerfrom yoursession.createandsession.updatepayloadsMake sure your
session.completecall includes the fullcustomerobject — email, phone, address
Note: If you use the Ingrid address form, customer data is collected inside the widget and you do not need to pass the customer object at session.complete. The address is captured automatically.
Step 3: Pass external_id on session complete
In V2, external_id — your order reference — is required on session.complete.
What to change:
Make sure your
session.completepayload includesexternal_idwith your order or reference ID
{
"checkout_session_id": "...",
"external_id": "YOUR-ORDER-ID",
"customer": { ... }
}
Step 4: Assess prefill_delivery_address
V2 introduces prefill_delivery_address on session.create. If you have customer address data available when checkout starts — from a logged-in profile or a cookie, for example — you can use it to pre-populate the address form.
What to decide:
Do you have address data available at session creation?
If yes, pass it in
prefill_delivery_addressonsession.createto speed up checkout for returning customers
This is optional, and recommended for a better customer experience.
Step 5: Replace session.get with session.pull
In V1, session.get regenerated delivery promises — it was mutating. In V2, session.get is idempotent: it returns a snapshot without refreshing anything.
To preserve V1 behaviour, use session.pull instead.
| V1 | V2 |
Refresh delivery promises |
|
|
Read session without side effects | — |
|
What to change:
Replace
session.getcalls withsession.pullwherever you need fresh delivery promises — for example when a customer returns to an abandoned checkoutUse
session.getonly when you need a read-only snapshot of current state, such as backend verification after frontend events
Step 6: Add quantity and discount to cart items
In V2, quantity and discount are required on cart items.
What to change:
Make sure every item in
cart.items[]includesquantityas an integer anddiscountas an integer in minor units — use0when there is no discount
{
"cart": {
"total_value": 50000,
"items": [
{
"sku": "SHIRT-001",
"name": "Classic T-Shirt",
"quantity": 2,
"price": 25000,
"discount": 0
}
],
"cart_id": "cart-123"
}
}
Step 7: Update response parsing from result to delivery_groups
The response structure has changed. What was result and selected_shipping_option in V1 is now delivery_groups[] in V2.
If you are not using split shipments, delivery_groups contains a single entry.
V1 response, simplified:
{
"session": {
"selected_shipping_option": { ... },
"result": {
"shipping_method": "postnord_home",
"delivery_type": "delivery",
"carrier": "PostNord",
"price": { "currency": "SEK", "price": 4900 },
"category": { "name": "Home Delivery" }
},
"search_address": { ... },
"customer": { ... }
}
}V2 response, simplified:
{
"session": {
"delivery_groups": [
{
"shipping": {
"carrier": "PostNord",
"product": "postnord_home",
"delivery_type": "delivery"
},
"pricing": {
"price": 4900,
"currency": "SEK"
},
"category": {
"name": "Home Delivery"
},
"addresses": {
"delivery_address": { ... },
"billing_address": { ... },
"customer": { ... },
"location": { ... }
},
"tos_id": "..."
}
]
}
}What to change:
Read from
delivery_groups[0]instead ofresultRead shipping data from
delivery_groups[0].shippingRead pricing from
delivery_groups[0].pricingRead
tos_idfromdelivery_groups[0].tos_id
Step 8: Update address reading from the response
In V1, search_address and customer were at the session root. In V2 they have moved inside delivery_groups[].addresses.
What to change:
Read the delivery address from
delivery_groups[0].addresses.delivery_addresswhen using the address formRead customer-provided data from
delivery_groups[0].addresses.customerRead the billing address from
delivery_groups[0].addresses.billing_address, if enabledRead pickup point details from
delivery_groups[0].addresses.location
Note: If the shopper uses the address form, delivery_groups[0].addresses.customer contains the data they provided. This is the authoritative address — persist it to your order management system.
API mapping — quick reference
What you need | V1 (SIW) | V2 (Delivery Checkout) |
Create session |
|
|
Get session, read-only | — |
|
Refresh delivery promises |
|
|
Update session |
|
|
Complete session |
|
|
Selected shipping method |
|
|
Carrier |
|
|
Delivery type |
|
|
Shipping price |
|
|
Category name |
|
|
Transport order ID |
|
|
Customer address |
|
|
Search address |
|
|
Delivery address, address form | — |
|
Pickup point |
|
|
FAQ
Do I need to migrate everything at once?
Yes. V1 and V2 are separate APIs, so you switch your integration from one to the other. There is no hybrid mode.Is V1 being deprecated?
V1 is already treated as legacy and is no longer actively maintained. Existing integrations continue to work, and no formal retirement date has been announced. All new capability — Ingrid AI, the address form, delivery groups, the return promise — is V2 only, so the practical answer is to plan a migration rather than wait for a deadline.Will my frontend break?
Widget embedding works the same way: you still receive anhtml_snippetand embed it. The JavaScript API (window._sw) and thedata_changed,suspend, andresumeevents remain available. V2 adds thesummary_changedevent for address form and delivery group use cases.What if I don't use split shipments?
delivery_groupscontains a single entry. Readdelivery_groups[0]wherever you previously readresult.Do I need to use the address form?
No, it is optional. If you prefer to handle address collection yourself, or through a payment provider, you can disable the address form and passsearch_addressviasession.update, as in V1. See How to structure an operationally viable and high-converting checkout with Ingrid Delivery Checkout for guidance on who should own the address form.What changes on session complete if I use the address form?
You no longer need to pass thecustomerobject — the address is already captured by the widget. You still passcheckout_session_idandexternal_id. Read the authoritative address fromdelivery_groups[0].addresses.delivery_addressin the response.What about the transport order (TOS)?
The session still completes as a transport order with all connected data. Ingrid Transport Administration works the same way whether V1 or V2 was used — it is not affected by this migration.Where can I find the full API reference?
Delivery Checkout API · SIW API, legacy
