Migrating to PostNL Shipment API v4, Step by Step

How to move a production PostNL integration to Shipment API v4: field mapping, barcode-to-reference tracking, key rotation, and rollback steps.

Migrating to PostNL Shipment API v4, Step by Step

Why this migration can't be deferred

PostNL is phasing out the contract-specific ProductCodeDelivery codes and the old three-call flow (Barcode, then Labelling, then Confirming) in favour of a single call that specifies explicit service flags. If you run PostNL as a direct integration rather than through a carrier abstraction layer, this is not an optional refactor. It's a PostNL API migration with three separate deadlines stacked on top of each other, and missing any one of them breaks label printing.

The core change is field-level. Product codes have been replaced with logical product names, so instead of using productCodeDelivery 3089, you now specify a parcel with services like signature and statedAddressOnly. The same logic appears in PostNL's own FAQ on the renewed APIs: you no longer need to work with codes like productCodeDelivery 3089; instead, you specify that you want a parcel with services like deliveryConfirmation: signature and statedAddressOnly, which makes pre-announcing shipments clearer and reduces errors.

Layered on top of that field change is a portal cutover with a hard date. As of 14 September 2026, the Mijn PostNL Zakelijk module in which you manage your API keys switches to a new environment, and current keys will remain active but will no longer be visible there, so you need to store them safely and in time. That date has already passed as I write this, which means if you haven't archived your keys yet, do it today, not after finishing the rest of this guide.

What you need before you start

Before touching production traffic, get four things in place.

  • Sandbox access and the Postman collection. All of PostNL's APIs are available as Postman collections, which is the fastest way to see the new payload shapes without reverse-engineering them from partial docs.
  • Your current production API key, retrieved and stored outside Mijn PostNL Zakelijk, ahead of the 14 September 2026 portal switch described above.
  • A written product-code mapping from your account manager. In your V2 configuration for Shipping, Labelling and Confirming, the product code (known in the API as "ProductCodeDelivery") must be changed according to PostNL's published schedule.
  • A pass/fail checklist for "it worked": a test-mode shipment returns a label (PDF or ZPL), a valid barcode, and a 2xx response, not error 610.

Step-by-step: the migration procedure

This is the order that avoids breaking production mid-flight. Treat each step as a gate, not a checkbox.

  1. Inventory every call site using the Barcode, Labelling and Confirming webservices as separate calls. Many integrations built years ago still do three round trips per shipment. PostNL's own documentation now recommends the all-in-one route: you can use the all-in-one Shipping API, which combines the functionality of the Barcode, Labelling, Confirmation and Easy Return API, generating unique barcodes at the same time you create a label and cutting down the number of API requests. Map every place in your codebase that still calls these separately before you touch mapping logic.
  2. Map old ProductCodeDelivery values to new service objects. Don't guess at this from general docs; use PostNL's published schedule for your specific contract. The worked example PostNL itself gives is the one to test against first: where you previously sent productCodeDelivery 3089, you now specify a parcel with services like signature and statedAddressOnly.
  3. Switch tracking lookups from barcode-keyed to reference-keyed. This is the part integrators miss most often, because it's not a payload field change, it's a lookup method change. If you use the shipping status API, be aware that the 'Barcode method' will not support the new automated generated barcodes — instead, you need the 'Reference method' to retrieve tracking information on shipment level. If your tracking dashboard or webhook consumer keys off the barcode string, that lookup silently stops returning data for any shipment created after cutover unless you switch the key.
  4. Update international ROW (Rest of World) shipments. If you generate barcodes for parcel ROW, drop the manual identifiers. When using automated barcode generation for parcel ROW, it's no longer needed to use your customer-specific Type (for example CL) and Range (for example 1234). If your code still constructs a barcode manually from Type and Range for ROW destinations, that logic is now dead weight and a source of validation errors.
  5. Split returns handling out into the Returns API v4. This is a structural change, not a field rename. Returns used to ride inside the shipment payload; PostNL has pulled them into their own endpoint. PostNL added a new API, the Return API v4.0, so returns no longer run through the Shipment API but through this separate, streamlined API. If your returns flow reuses shipment-creation code, it needs its own call path now.
  6. Run parallel (shadow) traffic in test mode against v4 before flipping production. Send the same order through both the old webservice and the new Shipment API v4 in your sandbox, and diff the label output, the barcode format, and the tracking response shape. Don't rely on "it returned 200" as your success signal, since a malformed service object can still validate and produce a wrong label.
  7. Rotate and archive your API key ahead of 14 September 2026, per the portal change already covered above. Do this independently of the product-code work; it's a credential-management task, not a payload change, and it can happen the same afternoon you read this.
  8. Cut over per contract and product code, not globally. The phase-out schedule is not a single flag day. Because of a simplification in PostNL's international product portfolio, a lot of contract-specific product codes from the international product portfolio have been phased out since 25 February 2026. Domestic and international product codes are on different clocks, so a global switch is more likely to break something than fix it.

A simplified before/after shows the shape of the change, without reproducing PostNL's schema verbatim:

// Before (legacy webservice, V2)
{
  "ProductCodeDelivery": "3089"
}

// After (Shipment API v4)
{
  "parcel": {
    "services": [
      { "type": "deliveryConfirmation", "value": "signature" },
      { "type": "statedAddressOnly", "value": true }
    ]
  }
}

The old version hides the business rule inside a numeric code you have to look up in a spreadsheet. The new version states the rule directly in the payload. That's a real improvement for anyone debugging a shipment six months from now, but it means every code-lookup table in your integration needs to become a code-to-service-object mapping instead.

Failure mode: the "Productcode is invalid" error

The most commonly reported break in this migration is error 610, "Productcode is invalid," which stops labels from printing entirely. It has shown up repeatedly on Shopify's community forum, where merchants describe the error code 610 "Productcode is invalid" appearing when creating or printing PostNL shipping labels, so labels can't be printed, typically right after a plugin or integration update.

The root cause is almost always one of two things: a code path still sending a deprecated bare product code instead of the new service-flag object, or a code sending a value that was retired outright. On the retirement side: a lot of contract-specific product codes from the international product portfolio have been phased out since 25 February 2026, with the new product codes offering the exact same service, label and rates. Same outcome, different price, same rates.

Don't retry a 610 blindly. This is a schema-versioning break, not a transient failure, and if your retry queue treats it like one you'll burn through backoff attempts sending the same broken payload. Add a contract-diff check in CI that flags any outgoing request still referencing a retired code, and fix the field before the request goes out again. Retries are only safe here once the payload itself is corrected; idempotency keys protect you from duplicate shipments, not from malformed ones.

Handling the transition window safely across multiple tenants

If you're running this migration once per shipper rather than once for your own single contract, stagger it. Product-code schedules are contract-specific, so a global switch that assumes every tenant is on the same code set will break the tenants who aren't.

Log both the old and new response shapes during the parallel-run period. If your observability stack only asserts on the fields it already knows about, it will silently swallow the new service-object structure and you'll find out about a schema drift from a support ticket instead of a dashboard. Keep the envelope (transport metadata, tenant ID, timestamps) separate from the payload (the actual PostNL request/response body) so you can version the payload independently per tenant without touching your routing logic.

Where PostNL is one of several carriers behind a routing layer, this is exactly the kind of carrier-specific churn that abstraction platforms are built to absorb. Multi-carrier tools such as Cargoson, nShift, Sendcloud and ShipEngine exist precisely so a shipper's own codebase doesn't have to track every carrier's field renames and endpoint splits directly. That's a genuine trade-off worth weighing against a direct integration, not a pitch, since direct integration still gives you first access to new PostNL features and full control over the retry and idempotency layer.

How you know the migration succeeded

Four checks, run against a full contract cycle rather than a single test order.

  • Zero calls in your logs to deprecated ProductCodeDelivery values.
  • Reference-method tracking returns full status history for every shipment created after cutover, with no gaps for shipments that used to resolve by barcode.
  • Your API keys are confirmed active in the new Mijn PostNL Zakelijk environment after the 14 September 2026 switch.
  • Label output (PDF or ZPL) and barcode format are unchanged from the customer's perspective, even though the request payload underneath looks nothing like it did before.

What's still moving: track & trace and Checkout v4

Don't treat this as a one-off project you close out and forget. PostNL's own timeline says the tracking side is still in flight: from the point of the FAQ's publication, customers can start working with PostNL's renewed Checkout and Warehouse APIs, while the Track & Trace APIs are still being rolled out. The Shipment API v4 work described in this guide is the part of the migration that's furthest along, but Checkout v4 and the track-and-trace consolidation are running on their own, later schedules.

Subscribe to PostNL's developer changelog rather than treating this guide as a fixed target. The pattern so far has been incremental: a product-code phase-out here, a barcode-format change there, a portal cutover on its own date. Build your monitoring to catch the next one before it turns into a 610 error in production.