---
title: "Migrating from Checkout V1 to V2"
canonical: "https://ingrid-support.refined.site/space/KB/754221057/Migrating%20from%20Checkout%20V1%20to%20V2"
format: markdown
---
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](https://support.ingrid.com/space/KB/754155521/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_changed` event 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** | `/v1/siw/` | `/v1/delivery_checkout/` |
| **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** | `session.get` regenerates delivery promises | `session.get` is idempotent, with no side effects · `session.pull` refreshes delivery promises |
| **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** | `customer` accepted on `session.create` and `session.update` | `customer` only on `session.complete` |
| `external_id` | Optional | Required on `session.complete` |
| **Cart item fields** | `quantity` and `discount` optional | `quantity` and `discount` required |
| **Response structure** | `selected_shipping_option` and `result` | `delivery_groups[]` array, with a single entry if there is no split shipment |
| **Address in response** | `search_address` and `customer` at session root | Inside `delivery_groups[].addresses` |
| **Frontend events** | `data_changed` | `data_changed` and `summary_changed`, with address form and delivery groups |
| **Ingrid AI support** | Not available | Predictive delivery times and profit-optimized delivery |
| `prefill_delivery_address` | Not available | Optional on `session.create` — prefills the address form with known customer data |

## 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** | `/v1/siw/` | `/v1/delivery_checkout/` |
| **Create** | `POST /v1/siw/session.create` | `POST /v1/delivery_checkout/session.create` |
| **Get** | `GET /v1/siw/session.get` | `GET /v1/delivery_checkout/session.get` |
| **Pull** | — | `GET /v1/delivery_checkout/session.pull` |
| **Update** | `POST /v1/siw/session.update` | `POST /v1/delivery_checkout/session.update` |
| **Complete** | `POST /v1/siw/session.complete` | `POST /v1/delivery_checkout/session.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 `customer` from your `session.create` and `session.update` payloads
- Make sure your `session.complete` call includes the full `customer` object — 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.complete` payload includes `external_id` with your order or reference ID

```json
{
  "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_address` on `session.create` to 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** | `session.get` | `session.pull` |
| **Read session without side effects** | — | `session.get` |

**What to change:**

- Replace `session.get` calls with `session.pull` wherever you need fresh delivery promises — for example when a customer returns to an abandoned checkout
- Use `session.get` only 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[]` includes `quantity` as an integer and `discount` as an integer in minor units — use `0` when there is no discount

```json
{
  "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:**

```json
{
  "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:**

```json
{
  "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 of `result`
- Read shipping data from `delivery_groups[0].shipping`
- Read pricing from `delivery_groups[0].pricing`
- Read `tos_id` from `delivery_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_address` when using the address form
- Read customer-provided data from `delivery_groups[0].addresses.customer`
- Read the billing address from `delivery_groups[0].addresses.billing_address`, if enabled
- Read 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 | `POST /v1/siw/session.create` | `POST /v1/delivery_checkout/session.create` |
| Get session, read-only | — | `GET /v1/delivery_checkout/session.get` |
| Refresh delivery promises | `GET /v1/siw/session.get` | `GET /v1/delivery_checkout/session.pull` |
| Update session | `POST /v1/siw/session.update` | `POST /v1/delivery_checkout/session.update` |
| Complete session | `POST /v1/siw/session.complete` | `POST /v1/delivery_checkout/session.complete` |
| Selected shipping method | `session.result.shipping_method` | `session.delivery_groups[0].shipping.product` |
| Carrier | `session.result.carrier` | `session.delivery_groups[0].shipping.carrier` |
| Delivery type | `session.result.delivery_type` | `session.delivery_groups[0].shipping.delivery_type` |
| Shipping price | `session.result.price.price` | `session.delivery_groups[0].pricing.price` |
| Category name | `session.result.category.name` | `session.delivery_groups[0].category.name` |
| Transport order ID | `session.tos_id` | `session.delivery_groups[0].tos_id` |
| Customer address | `session.customer` | `session.delivery_groups[0].addresses.customer` |
| Search address | `session.search_address` | `session.delivery_groups[0].addresses.search_address` |
| Delivery address, address form | — | `session.delivery_groups[0].addresses.delivery_address` |
| Pickup point | `session.result.location` | `session.delivery_groups[0].addresses.location` |

## 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 an `html_snippet` and embed it. The JavaScript API (`window._sw`) and the `data_changed`, `suspend`, and `resume` events remain available. V2 adds the `summary_changed` event for address form and delivery group use cases.

**What if I don't use split shipments?**  
`delivery_groups` contains a single entry. Read `delivery_groups[0]` wherever you previously read `result`.

**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 pass `search_address` via `session.update`, as in V1. See [How to structure an operationally viable and high-converting checkout with Ingrid Delivery Checkout](https://support.ingrid.com/space/KB/461373442/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 the `customer` object — the address is already captured by the widget. You still pass `checkout_session_id` and `external_id`. Read the authoritative address from `delivery_groups[0].addresses.delivery_address` in 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](https://developer.ingrid.com/delivery_checkout/) · [SIW API, legacy](https://developer.ingrid.com/siw/api/)

## Related pages

- [Ingrid checkout API options: V1, V2, and Headless](https://support.ingrid.com/space/KB/754155521/Ingrid+checkout+API+options+V1+V2+and+Headless)
- [Ingrid Delivery Checkout](https://support.ingrid.com/space/KB/11501671/Ingrid+Delivery+Checkout)
- [Ingrid Address form](https://support.ingrid.com/space/KB/536150018/Ingrid+Address+form)
- [Checkout implementation](https://support.ingrid.com/space/KB/11501718/Checkout+implementation)
- Developer documentation: [Delivery Checkout](https://developer.ingrid.com/delivery_checkout/) · [SIW, legacy](https://developer.ingrid.com/siw/api/) · [Address form](https://developer.ingrid.com/delivery_checkout/features/address_form/)