- Checkout
- Features
Reserve and update the shipment
Implement POST /shipment and PUT /shipment/{shipment_id} for the Kustom Shipping Assistant: reserve on Buy, update on a retry, refuse with failure_reason.
When the customer presses Buy, Kustom asks your TMS to reserve the delivery they chose. Pressing
Buy isn’t the end of the purchase: the payment can still be declined, and the customer can then
change anything and press Buy again. So the first attempt creates a reservation with
POST /shipment, and every later attempt updates that same reservation with
PUT /shipment/{shipment_id}. Both are required.
When Kustom calls it
Section titled “When Kustom calls it”| The customer | Kustom calls |
|---|---|
| Presses Buy for the first time | POST /shipment |
| Presses Buy again, after the payment failed or they changed something | PUT /shipment/{shipment_id}, with the id you returned |
| Presses Buy again within 5 minutes, with nothing changed | Nothing. Kustom reuses your last answer |
Kustom doesn’t call either for a checkout that has fallen back to the merchant’s options, or for a digital-only order.
Neither call means the purchase happened. You’ll learn that from
POST /shipment/{shipment_id}/confirm,
which may never come: a customer who gives up after a declined payment leaves a reservation that
is never confirmed. Hold reservations, don’t book them.
Request
Section titled “Request”POST /shipmentContent-Type: application/vdn.klarna.shipping.select_option-v1+jsonAuthorization: Bearer <token>The body is the same session_id, locale, currency, recipient and order as on
POST /shippingoptions, with
the address now complete, plus the option the customer chose. order is shortened here:
{ "session_id": "f7b7dda8-c9a6-405d-8cca-1c3cbdfda1c0", "locale": "sv-SE", "currency": "SEK", "recipient": { "given_name": "Klara", "family_name": "Joyce", "street_address": "Sveavägen 42", "postal_code": "111 34", "city": "Stockholm", "country": "SE", "phone": "+46708800000", "email": "customer@example.com" }, "order": { "id": "17d4cd21-99cd-4ffa-97d6-257b258cf053", "total_amount": 79900, "total_weight": 890 }, "selected_shipping_option": { "id": "pn-pickup", "type": "pickup-point", "class": "standard", "carrier": "postnord", "name": "PostNord pickup point", "price": 2900, "tax_rate": 2500, "location": { "id": "loc-4", "name": "Odenplan parcel locker", "address": { "street_address": "Odenplan 1", "postal_code": "113 22", "city": "Stockholm", "country": "SE" }, "coordinates": { "lat": 59.3431, "lng": 18.0492 } }, "addons": [ { "type": "sms", "required": false, "price": 0, "data": { "text": "+46708800000", "selected": true } }, { "type": "carbon-compensated", "required": false, "price": 500, "data": { "selected": false } } ] }}selected_shipping_option is the option as you returned it, narrowed to the customer’s choice:
| Field | Meaning |
|---|---|
id, type, class, carrier, name, tax_rate, free_above, features | As you returned them |
price | What the customer pays, tax included: the chosen time slot’s or location’s price if it has one, otherwise the option’s, plus every add-on they selected. Check it against your own calculation |
location | The pickup point they chose: id, name, address, coordinates |
timeslot | The time slot they chose: id, start, end, cutoff, price |
addons | Every add-on the option offered. data.selected says whether the customer took it, and data.text holds what they typed, or the phone number or email for a notification |
An add-on carries the id and url you gave it, if any. location and timeslot are absent
when the option has none.
Response
Section titled “Response”Answer 200 or 201 with your reservation:
{ "shipment_id": "SHP-100234", "selected_shipping_option": { "carrier": "postnord", "carrier_product": { "name": "MyPack Collect", "identifier": "19", "addon_identifiers": ["A1"] } }, "shipments": [ { "carrier": "postnord", "tracking_id": "00370712345678901234" } ]}| Field | Required | Meaning |
|---|---|---|
selected_shipping_option | Yes | Without it, the reservation counts as failed |
selected_shipping_option.carrier | No | Defaults to generic. Must be one of the carrier values if present |
selected_shipping_option.carrier_product | No | The carrier’s product code, name, and any addon_identifiers. Passed on to the merchant’s order with the delivery details |
shipment_id | Yes, in practice | Your id for the reservation. Kustom uses it for PUT and /confirm, and the merchant receives it as tms_reference to book against. Without it, every retry is a new POST, and nothing after the purchase reaches you |
shipments | No | One entry per parcel: carrier, tracking_id, tracking_url. A malformed tracking_url rejects the response |
A shipment_id must be unique per session_id. Kustom doesn’t read the Location header, so
you don’t need to send one.
Make POST /shipment safe to repeat
Section titled “Make POST /shipment safe to repeat”Kustom may repeat a POST /shipment after a network error, so the same request can reach you
twice, and it sends no idempotency key. Key reservations on session_id: when a second
POST /shipment arrives for a session you already hold a reservation for, update that
reservation and return the same shipment_id instead of creating another.
Updating with PUT
Section titled “Updating with PUT”PUT /shipment/{shipment_id}Content-Type: application/vdn.klarna.shipping.select_option-v1+jsonAuthorization: Bearer <token>Same body, same response. Anything can have changed since the POST: the address, the cart,
the option, the pickup point or the add-ons. Replace the reservation with what you receive.
Return the same shipment_id.
If you answer 404, Kustom sends the request as a new POST /shipment instead. That’s the
escape hatch for a reservation you’ve already discarded, not a way to skip implementing PUT:
every retry would leave the previous reservation behind in your system.
Refusing a reservation
Section titled “Refusing a reservation”When you can’t reserve what the customer picked, answer with a 4xx and a reason:
{ "failure_reason": "shipping_option_not_available" }failure_reason | The customer sees |
|---|---|
shipping_option_not_available | “Your shipping option is unavailable” — pick another option and buy again |
cutoff_time_expired | “Cutoff time is expired” — pick another option and buy again |
shipping_address_invalid | “Check your delivery address” — fix the address and buy again |
| anything else, or none | “Your shipping option is unavailable” |
The purchase stops there. Kustom asks you for options again with
POST /shippingoptions, so return options that reflect why you refused, and the customer picks
again. If that request fails too, the checkout falls back to the merchant’s own options.
A 5xx, a timeout or a response Kustom can’t read is treated the same way, as a reason-less
refusal. A 401 makes Kustom fetch a new token and try once more first.
Related
Section titled “Related”- Confirm the shipment — what happens once the purchase completes
- Return shipping options — the option objects this call sends back
POST /shipmentin the API reference
This page is also available as Markdown — append .md to its URL — and is indexed in llms.txt.