Integrating 20+ courier partners: what we learned

We currently run integrations with more than twenty courier partners — FAN, Cargus, Sameday, DPD, GLS, Dragon Star, Speedy, Econt, MyGLS, DHL Express, DHL eCommerce, NovaPosta, InPost, Meest, Elta, Express One BG, SpeedyBG, OverseasHR, MilspedSerbia, PallEx, DSC, ECSC, Speedex, and a few we add on request. None of them implement the same API. Most don’t agree on what a delivery looks like as a data structure. Several change their endpoints without notice.

If you’re building integrations to even one partner, the lessons that scale to twenty are worth knowing up front.

Standardize the status model first, integrate second

The single highest-leverage decision we made early on was to define a standardized 8-status model that every external integration translates into. It looks like this: created, picked-up, in-transit, in-hub, out-for-delivery, delivered, exception, returned. Every external partner, no matter how baroque their own status taxonomy, gets normalized into one of these eight states.

Some partners ship with twenty-plus status codes. Some ship with five. Some emit “delivered to neighbor” as a separate status; some emit it as a free-text note on a generic “delivered” event. Without the standardized layer, every downstream consumer of the data — your dashboard, your client portal, your billing system, your COD reconciliation — has to know about every partner’s quirks. With the standardized layer, downstream systems consume one model and you push the translation upstream into the integration layer.

This is the difference between an integration that takes a week and an integration that takes a month — and between a system that’s debuggable in production and one that isn’t.

Push callbacks are the goal; polling is the fallback

Most courier partners offer both webhook (push) status updates and a tracking-status pull endpoint. Where we have the choice, we use push. Where the partner doesn’t offer push, we poll on a schedule that respects their rate limits — typically every fifteen minutes for in-transit shipments, escalating to every two minutes for shipments expected to deliver in the current window.

The hidden cost of polling: at twenty partners and tens of thousands of in-flight shipments per partner, you can quickly build a system that spends most of its CPU asking external APIs for state changes that haven’t happened. The trick is bucketed polling — group shipments by their expected delivery window and only poll the buckets where state change is plausible.

A second hidden cost: rate-limit retry storms. When a partner API throttles you, naive retry logic compounds the problem. Every integration we run has its own circuit-breaker and backoff curve, with the parameters tuned to the partner’s documented (and undocumented) limits.

Status-code translation is a living artifact

The translation table for each partner — their codes to our 8-status model — is not something you write once and forget. Partners add new codes, retire old ones, change semantics without changing strings, and occasionally start emitting an existing code in a different context. Once a quarter, somewhere in our partner network, something changes.

We treat the translation table as code: version-controlled, code-reviewed, with regression tests against captured real-world payloads. When a new code appears in production, the integration emits an “unmapped status” exception that surfaces to operations within an hour, not within a customer complaint.

This sounds heavy until you’ve been bitten by the alternative — the silent failure where a status emits, the parcel gets stuck in an unmapped state, and you find out about it because a client’s dashboard shows an in-transit count that hasn’t moved for ten days.

Designing around the partners that don’t have an API

Three of our integrations are with partners who didn’t have a usable API when we started. The pattern that worked: bidirectional CSV exchange via SFTP, normalized through the same 8-status model, polled hourly, with a manual override flow for operations to flag obvious mismatches. Two of those three have since shipped real APIs and we’ve migrated; one is still happily on CSV. Both flows hit the same standardized layer.

The lesson: don’t let “they don’t have an API” be the reason an integration doesn’t ship. The integration layer is what’s standardized, not the partner.

What breaks at scale

At twenty partners and several million shipments a month aggregated across customers, the failure modes are:

  • Schema drift — partners change a field type without notice (string to int, single to array). We’ve started running schema validation on inbound webhooks and rejecting payloads that don’t match the contract.
  • Authentication churn — token rotations that aren’t documented, client-credential resets after partner internal changes. Every integration has a credential-health monitor that alerts when auth starts failing in patterns that aren’t request-by-request.
  • Time-zone confusion — partners that emit UTC mixed with partners that emit local without a tz indicator, partners that switch between the two depending on the endpoint. Every timestamp gets normalized at the integration boundary; nothing downstream trusts an unnormalized timestamp.
  • Idempotency failures — partners that don’t deduplicate webhook delivery and emit the same event five times in a minute. Every webhook has an idempotency key and the platform deduplicates internally.

What this looks like for our customers

When a CourierManager customer adds a partner to their setup, the integration is already built, already maintained, and already mapped to the standardized status model. They turn it on with a credential pair and a routing rule. They don’t write integration code; they don’t maintain a translation table; they don’t get paged at 2 AM when a partner ships a new status code.

This is the actual product. The features page lists “20+ integrations.” The reason that number matters is that the customer doesn’t have to build any of them.


Coordinating multiple courier partners under one brand? See the integrator platform → — the same architecture that makes 20+ integrations tractable on our side is what makes orchestration tractable on yours.