Circuit Breakers in Carrier Integration, Explained

What the circuit breaker pattern is, how it differs from retries, and how carrier integration platforms apply it per carrier.

Share
Circuit Breakers in Carrier Integration, Explained

What a Circuit Breaker Actually Is

A circuit breaker is a stateful guard placed in front of a call to a dependency, such as a carrier's tracking or label API, that stops sending requests once failures cross a defined threshold. Instead of your adapter waiting on calls that are almost certainly going to fail, it fails fast and returns a fallback immediately. This is the core of the circuit breaker pattern in carrier integration software.

The pattern runs as a small state machine with three states. In the Closed state, calls flow through normally and the breaker counts failures. In the Open state, calls are rejected immediately without touching the carrier at all. In the Half-Open state, a handful of probe calls test whether the dependency has recovered. As Microsoft's own description puts it, the request from the application is routed to the operation in the Closed state, the proxy maintains a count of recent failures, and if the number of recent failures exceeds a specified threshold within a given time period, the proxy is placed into the Open state and starts a time-out timer. When the timer expires, the proxy is placed into the Half-Open state.

On a carrier platform this guard typically sits inside the adapter layer, one instance per carrier endpoint, watching calls to DPD, UPS, PostNL or DHL and tripping independently of every other carrier the platform talks to.

Circuit Breaker vs Retry: Not the Same Job

Retry and circuit breaker solve different problems, and conflating them is the single most common mistake in carrier adapter code. Retry assumes a failure is transient and tries the same call again; a circuit breaker assumes the dependency is unhealthy and stops calling it altogether.

In Azure's own framing, which is widely quoted because it is precise: the Retry pattern enables an application to retry an operation in the expectation that it'll succeed, while the Circuit Breaker pattern prevents an application from performing an operation that is likely to fail. The two are not competing choices, they compose. An application can combine these two patterns by using the Retry pattern to invoke an operation through a circuit breaker, however the retry logic should be sensitive to any exceptions returned by the circuit breaker and abandon retry attempts if the circuit breaker indicates that a fault is not transient.

Rate limiting is a third, separate concern. A rate limiter protects your system (or the carrier's) from too much traffic; a circuit breaker protects your system from an unhealthy dependency regardless of volume. The Circuit Breaker allows monitoring of failure and thus stops sending requests to services that are in a failure state for a given amount of time to enable recovery, while a Rate Limiter controls the amount of requests to prevent overloading the system. You can trip a rate limiter against a perfectly healthy carrier API simply by sending too many label requests during a sales peak. You trip a circuit breaker only when the carrier itself starts failing.

PatternAssumesActionTypical trigger in carrier integration
Retry with backoffFailure is transientRe-attempt the same call, usually with jitter and increasing delayOne dropped TCP connection to a carrier endpoint
Circuit breakerDependency is unhealthyStop calling it, fail fast, return a fallbackDPD Germany's tracking API returning errors on most calls for several minutes
Rate limiterYou are sending too much trafficThrottle outbound calls to stay under a quotaA label storm during a Black Friday peak hitting your own carrier contract limits
BulkheadOne workload could exhaust shared resourcesIsolate thread/connection pools per dependency or tenantOne tenant's PostNL calls should not starve another tenant's DHL calls

The Three States, in Carrier-Adapter Terms

Applied to a multi-carrier platform, the states map directly onto per-endpoint adapter behaviour. Closed means requests to a carrier's tracking or rating endpoint are routed as normal. Open means that one specific adapter, say DPD Germany tracking, fails fast and returns a cached or last-known value while every other carrier adapter keeps working unaffected. Half-Open means a small number of probe requests go through after the wait period after a configured wait duration, lets a limited number of test requests through, and if they succeed, the circuit closes, if they fail, it opens again.

The failure counter itself is windowed, not cumulative. A breaker configured with a 10-call sliding window and a 50% failure threshold does not trip on a single bad call from last week; it looks at the last 10 calls, and if 50% or more fail, after at least 5 calls, the circuit opens. That minimum-calls guard matters more than people expect, because a low-traffic carrier endpoint (say, a regional courier your platform routes rarely) can otherwise trip on two or three unlucky calls out of five.

Worked Example: DPD Germany and UPS, October 2026

Shippo's public status page gives a clean, real illustration of exactly the partial failure a circuit breaker exists to isolate. On 10 October 2026, Shippo reported: we are currently observing an elevated error rate on DPD Germany's Tracking API, tracking information will be unavailable; label generation and rating are not impacted. At the same time, the carrier status board showed the Carrier API in Partial Outage, with USPS Operational, UPS Partial Outage, FedEx Operational, and DPD DE in Partial Outage. A day later, a separate incident appeared: an interruption in UPS tracking events from the carrier, with the team reaching out to UPS and monitoring, and tracking updates delayed but sent once the carrier's API is restored.

Walk through what a correctly scoped breaker layout does with this exact pair of incidents:

  • The DPD Germany tracking breaker trips open after its failure threshold is crossed. Requests for tracking status return the last known status from your own store instead of hammering a carrier endpoint that is already struggling.
  • The DPD Germany label and rating breaker stays closed, because those endpoints are a different circuit entirely and Shippo's own incident notes confirm label generation and rating were not impacted.
  • The UPS tracking breaker trips independently of DPD, on its own failure window, because UPS and DPD are unrelated dependencies with unrelated root causes.
  • Every other carrier adapter, FedEx, USPS, DPD UK, Airterra, remains fully closed and unaffected, because breaker state is not shared across carriers.

That last point is the design lesson hiding in this incident: breakers must be scoped per endpoint, not per carrier and certainly not per platform. A single breaker wrapping "DPD" as a whole would have needlessly blocked label purchases during a tracking-only incident, which is a worse outcome than the original fault.

A Resilience4j configuration that matches this kind of carrier-call profile looks like this:

resilience4j.circuitbreaker.configs.dpdTracking.sliding-window-size=10
resilience4j.circuitbreaker.configs.dpdTracking.minimum-number-of-calls=5
resilience4j.circuitbreaker.configs.dpdTracking.failure-rate-threshold=50
resilience4j.circuitbreaker.configs.dpdTracking.wait-duration-in-open-state=5s
resilience4j.circuitbreaker.configs.dpdTracking.permitted-number-of-calls-in-half-open-state=5
resilience4j.circuitbreaker.configs.dpdTracking.automatic-transition-from-open-to-half-open-enabled=true

Translated: look at the last 10 calls, if 50% or more fail after at least 5 calls the circuit opens, after 5 seconds it transitions to half-open and lets 5 test requests through. Carrier APIs vary widely in latency and baseline error rate, so these numbers need tuning per endpoint rather than copy-pasted across every carrier adapter you own.

Why Multi-Tenant Platforms Need a Blast-Radius Decision, Not Just Breaker Code

The harder design question on a multi-tenant carrier platform is rarely "do we have a circuit breaker somewhere." It is "what is the blast radius of one breaker tripping": per carrier, per tenant, per endpoint, or per credential pool. Get the scope wrong and a single degraded carrier can take down shipments for tenants who were never calling that carrier in the first place.

This is also where breakers can misfire in the opposite direction. In cell-based or sharded architectures, a breaker that treats a partial regional failure as a signal about the whole dependency can trip globally when only one shard or one tenant pool was actually affected, turning a contained problem into a platform-wide one. The composition order that avoids this generally runs bulkhead first, then circuit breaker, then retry, then timeout, because the bulkhead, as the outermost layer, isolates the blast radius so that even if one dependency's circuit is open, other dependencies continue functioning. If you are building carrier middleware for more than a handful of shipper tenants, this ordering is not optional polish, it is the difference between one tenant's DPD outage and every tenant's dashboard going dark.

Where This Shows Up in Practice

Every production carrier-connectivity platform needs this isolation logic somewhere in its adapter layer, whether it is a commercial multi-carrier API like EasyPost or ShipEngine, a European platform like nShift or Shippo, or middleware such as Cargoson routing shipments across dozens of regional couriers. A single degraded carrier should never take unrelated shipments down with it, and that guarantee has to be built into the adapter, not bolted on as an afterthought.

Most teams do not build the state machine from scratch. Martin Fowler's own account of the pattern notes that if you have many callers on an unresponsive supplier, you can run out of critical resources leading to cascading failures across multiple systems, and in his book Release It, Michael Nygard popularized the Circuit Breaker pattern to prevent this kind of catastrophic cascade. In practice, you will reach for an existing library rather than reimplement that state machine: Resilience4j and the now-legacy Netflix Hystrix on the JVM, Polly on .NET, or a service-mesh-level breaker if you are running Envoy or similar at the infrastructure layer.

FAQ

Is a circuit breaker the same as a timeout? No. A timeout bounds a single call. A circuit breaker remembers history across many calls and changes behaviour for calls that haven't happened yet.

Does a circuit breaker replace retries? No. A retry attempts a failed operation again on the assumption it will eventually succeed; a circuit breaker stops sending requests once a dependency is consistently failing. Production systems typically run both, with retry logic that respects the breaker's state rather than retrying straight through an open circuit.

What should the fallback response be for a tripped carrier breaker? A cached rate, the last known tracking status, or an explicit "carrier unavailable" error. Never a silent default that looks like a successful response, since that turns an outage into a data-quality problem downstream.

How many failures should trip the breaker? It depends on call volume. Set a minimum-number-of-calls floor so low-traffic carrier endpoints don't trip on two or three unlucky calls out of five, as described above with the 50%-of-at-least-5-calls pattern.

Where did the pattern come from? Michael Nygard described it in his 2007 book Release It!, and Martin Fowler later wrote his own widely referenced account of the pattern on his bliki.