ShipX PL to InPost Global API, Step by Step
A reproducible guide to migrating InPost ShipX PL to the Global API: OAuth 2.1 scopes, PKCE, tracking remapping, and HMAC webhook cutover.
InPost is folding every country-specific API it has ever shipped into one surface, and if your integration still talks to ShipX PL, you are on a clock. This guide walks through the actual mechanics of an InPost API migration: registering an OAuth 2.1 client with PKCE, re-pointing shipment and label calls to the InPost Global API, remapping tracking statuses, and cutting your webhook verification over to HMAC-SHA256. No theory, just the sequence we'd run on a live tenant.
Why this is forcing itself onto your backlog
InPost is consolidating its services under a single REST API, the Global API, which covers core functionalities such as shipment creation and manifestation while tracking is aligned with the global strategy over time. This isn't a feature release you can defer. It's a platform consolidation, and the rollout order tells you who gets priority support.
The initial focus is on merchants with logistics contracts integrated with Poland and its local APIs. If you're running ShipX PL today, you're in the first wave. Italy moves to api-shipx-it.easypack24.net, UK to api-uk.easypack24.net, and Mondial Relay to connect-api.mondialrelay.com in later phases, so if you operate across those markets too, plan for staggered cutovers rather than one big-bang weekend.
We've covered forced-cutover migrations on this blog before for larger parcel carriers. This one is smaller in volume for most shippers, but the authentication and webhook changes are substantial enough that a copy-paste migration will break silently, usually on a bank holiday when nobody's watching the logs.
What you need before you start
Don't open a sandbox and start improvising. Get these four things lined up first:
- A registered OAuth 2.1 client (client_id and client_secret) through the InPost Developer Portal, with scopes decided in advance rather than requested wholesale.
- A sandbox Global API credential separate from your production ShipX PL token, so you can run both integrations side by side.
- A parallel-run window of at least one full shipment lifecycle, from label creation to final delivery status, before you touch production traffic.
- A webhook endpoint that can verify two signature schemes simultaneously, because the deprecated asymmetric method and the new HMAC method will both be live during your transition.
The available scopes are api:points:read, api:shipments:read, api:shipments:write, api:returns:read, api:returns:write, api:one-time-pickups:read, api:one-time-pickups:write, and api:tracking:read. Request only what your integration actually calls. A label-only service account doesn't need api:returns:write, and an auditor will ask why it has it.
Migrating step by step
This is the sequence we'd follow for a single-tenant cutover. If you're running multi-tenant carrier middleware, wrap each step in a feature flag per tenant so you can roll back one shipper without touching the rest.
- Register your client and scope it narrowly. Pick the exact scopes your integration needs from the list above. Requesting everything "to be safe" just widens your blast radius if a credential leaks.
- Implement Authorization Code Flow with PKCE. InPost documents this as Option 2, the Authorization Code Flow with Proof Key for Code Exchange, best suited for broker applications that let users consent to acting on their behalf. Even for confidential server-to-server clients, use PKCE: InPost's API is compliant with OAuth 2.1, which requires PKCE in this authorization method as an extension that enhances security of the authorization code flow and protects authorization codes from interception attacks.
- Exchange the code and parse the token response exactly. The PKCE flow returns access_token, refresh_token, scope, token_type, and expires_in: 299 seconds. That's roughly five minutes, not the hour you might be used to from other carrier APIs. Build your refresh logic around this number, not a guess.
- Re-point shipment creation and label endpoints to the Global API base URL. Keep ShipX PL read-only during the transition so you have a fallback for status lookups without risking duplicate label generation.
- Build a tracking status translation table before cutover. ShipX PL's string-based statuses don't map one-to-one onto Global Tracking's event codes. As a concrete example, the ShipX PL status accepted_from_courier_by_warehouse and adopted_at_sorting_center both map to the Global Tracking status MMD.1001, generic Adopted at Logistics Centre, and canceled maps to EOL.9004, Cancelled. Several ShipX PL statuses collapse into the same generic code, and some split into multiple events depending on condition flags. Pull the full mapping table and write the translation layer first, not after you've gone live and your dashboard shows blank statuses.
- Switch webhook subscriptions to HMAC. InPost uses secret keys exchanged with a client and the HMAC-SHA256 algorithm to generate a signature of the message, included in the x-inpost-signature header. The content you sign depends on configuration: it's either the raw request body, or a timestamp taken from the x-inpost-timestamp header followed by a dot separator and the request body, with the subscription configuration determining which of the two is used. Whichever mode you pick, compute the signature over the exact bytes InPost sent, before any JSON parsing, because deserializing and re-serializing can reorder keys or change whitespace and silently break verification. Encode as UTF-8, run HMAC-SHA256 with your shared secret, and base64-encode the result before comparing it against the header.
- Check the payload version header on every webhook call. Every webhook call carries the payload version in the x-inpost-api-version header, formatted as a date, for example 2024-06-01. If a change is non-breaking, such as a new field being added, no new version is introduced, and your integration should simply ignore fields it doesn't recognise. Build your deserializer to tolerate unknown fields from day one.
- Run both integrations in shadow mode. Mirror a copy of production traffic through the Global API without acting on its output, for at least one complete shipment lifecycle, before you decommission ShipX PL credentials. This is the step teams skip under deadline pressure, and it's the one that catches the tracking status gaps step five was supposed to prevent.
Failure mode 1: short token TTL breaks label-storm batch jobs
A 299-second access token is fine for request-response traffic. It is not fine for a nightly batch job that generates 40,000 labels in sequence, because the token will expire mid-run.
The fix is to refresh proactively, not reactively. Refresh at roughly 70% of the token's lifetime (around the 200-second mark) rather than waiting for a 401 response. That gives you a buffer for network latency and avoids a retry storm hitting the token endpoint at the exact moment thousands of in-flight label requests all fail together.
The second half of the fix is idempotency. If a shipment-create call times out because the token expired mid-request, and your batch job retries after refreshing, you need a stable idempotency key on that call so the retry doesn't produce a second label and a second booked collection. This is the same pattern we've argued for elsewhere on this blog: idempotency keys aren't an InPost-specific concern, but a 5-minute token window makes the gap between "call sent" and "call acknowledged" wide enough that you'll hit it in production if you haven't already.
Failure mode 2: duplicate webhook deliveries during the cutover window
For a period, your tenant will have both signature schemes live at once. InPost supports two methods of signing webhooks: an HMAC signature generated using a shared secret key, and a digital signature generated based on a rotated InPost public key, which is deprecated. If your subscription management isn't precise about which scheme is attached to which endpoint, you can end up with the same event delivered twice, once signed with each method, and processed twice downstream.
The fix isn't to get cleverer about signature verification. It's to de-duplicate on event ID, not on signature method. Treat the signature as an authenticity check, and treat the event ID as your idempotency boundary for processing. Every webhook consumer you build for carrier integration software should already do this regardless of migration status, but a dual-signature cutover window is exactly when the gap gets exposed if you haven't.
How you know it worked
Don't call the migration done on vibes. Check these four things over a 24-hour window:
- Zero 401 responses on token refresh, confirming your 70%-TTL refresh timing is actually landing before expiry.
- 100% webhook signature verification success against your HMAC secret, with zero fallback attempts against the deprecated asymmetric method.
- Your tracking dashboard showing Global Tracking's PREFIX.CODE event format exclusively, with no legacy ShipX PL status strings leaking through from a forgotten fallback path.
- ShipX PL credentials revoked with no drop in shipment volume or tracking coverage, which is your real proof that the cutover is complete rather than partially shadowed.
Where this fits in a multi-carrier stack
If you're running one carrier, you own this migration project outright. If you're running fifteen, this is exactly the kind of churn that multi-carrier shipping software is built to absorb, because the carrier-specific authentication change, the status remapping, and the signature scheme swap all happen behind one abstraction layer instead of in your codebase.
| Platform | Couriers behind one API | Webhook/event model | Note |
|---|---|---|---|
| Sendcloud | 170+ couriers, including InPost, through one credential set, one schema, one webhook stream, and one error model | Unified tracking taxonomy | Direct comparison point used above |
| EasyPost | Not published | Unified webhook events | Broad carrier panel, US-centric roots |
| ShipEngine | Not published | Unified webhook events | Developer-first REST API, similar abstraction model |
| Cargoson | Not published | Unified tenant-facing API | Documents InPost connectivity directly; see their InPost API documentation |
None of these replace the need to understand what's happening underneath. If you're the one building or operating the middleware layer, the steps above are the ones you'll implement regardless of which abstraction sits on top.
Next steps
Start with the authentication swap in sandbox this week, not the tracking remap. Getting OAuth 2.1 PKCE and the 299-second token lifecycle right is the dependency everything else sits on. Once that's stable, pull the full ShipX PL to Global Tracking status table from InPost's developer portal and build your translation layer before you re-point a single production shipment call. Verify your webhook consumer against both signature schemes using the signature verification guide before you flip a single tenant over, and don't revoke ShipX PL credentials until your shadow-mode run has covered one complete shipment lifecycle end to end.