Every integration you build depends on systems you do not control. The payment gateway, the shipping carrier, the ERP, the Shopify Admin API – each of them will, at some point, be slow, return a 503, throttle you with a 429, or simply disappear for ten minutes. The question is not whether a third-party API fails, but what your application does when it does. Out of the box, HttpClient waits up to 100 seconds and tries exactly once. That is rarely what you want. Here is how to make outbound API calls resilient in .NET 8 without writing retry loops by hand.
What actually goes wrong
- Slow responses – a request that hangs ties up a thread and a waiting user, and often fails anyway.
- Transient errors – a 502, a 503 or a dropped connection that would succeed if you simply tried again a moment later.
- Rate limits – a 429 with a
Retry-Afterheader telling you exactly how long to wait. - Real outages – the API is down, and hammering it with retries only makes your own system slower.
Each of these needs a different response: a timeout, a retry, a polite wait, or a fast failure. Handling all four consistently is what separates an integration that wakes someone up at 3 a.m. from one that heals itself.
Start with the standard resilience handler
Microsoft ships a resilience package built on Polly that plugs straight into IHttpClientFactory. One line gives a typed client a complete pipeline: a concurrency limiter, a total request timeout, retries with exponential backoff and jitter, a circuit breaker and a per-attempt timeout.
dotnet add package Microsoft.Extensions.Http.Resilience
builder.Services
.AddHttpClient<CarrierApiClient>(client =>
client.BaseAddress = new Uri("https://api.example-carrier.com/"))
.AddStandardResilienceHandler();
Retries only fire for failures worth retrying: network errors, timeouts, 5xx responses, 408 and 429. A 400 or a 404 fails immediately, because sending the same bad request again will not fix it.
Tune it for the API you are calling
The defaults are a good starting point, but every API has its own personality. A rate-quote endpoint that a customer is waiting on at checkout deserves a short timeout; a nightly export can afford to be patient. Override only what you need:
.AddStandardResilienceHandler(options =>
{
// Give up on one slow attempt quickly...
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(4);
// ...but cap the whole call, retries included.
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(20);
options.Retry.MaxRetryAttempts = 3;
options.Retry.Delay = TimeSpan.FromMilliseconds(500);
// Stop calling an API that is clearly down.
options.CircuitBreaker.FailureRatio = 0.5;
options.CircuitBreaker.MinimumThroughput = 10;
options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(30);
options.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds(15);
});
Two details matter here. The circuit breaker’s sampling window must be at least twice the attempt timeout, or the options fail validation at startup. And while the circuit is open, calls fail instantly with a BrokenCircuitException instead of waiting – which is exactly what you want, because it gives you a clean place to fall back:
try
{
return await _carrierApi.GetRatesAsync(shipment, cancellationToken);
}
catch (BrokenCircuitException)
{
// Carrier is down: answer from the last known rates instead of making the customer wait
return await _rateCache.GetLastKnownRatesAsync(shipment, cancellationToken);
}
Never blindly retry a POST
Retrying a GET is harmless. Retrying a POST that creates a shipment or charges a card can do it twice – the first attempt may have succeeded and only the response was lost. If the API offers no protection against duplicates, switch automatic retries off for unsafe methods:
.AddStandardResilienceHandler(options =>
options.Retry.DisableForUnsafeHttpMethods()); // no automatic retries for POST, PUT, PATCH, DELETE
If the API does accept an idempotency key – Stripe is the best-known example, and more payment and logistics APIs support one every year – keep retries on and send a key derived from your own record. The provider then recognises the repeat and returns the original result instead of creating a second one:
var request = new HttpRequestMessage(HttpMethod.Post, "v1/shipments")
{
Content = JsonContent.Create(shipment)
};
request.Headers.Add("Idempotency-Key", $"shipment-{order.Id}");
The same idea protects the receiving side too: we showed how a unique database constraint keeps webhook processing safe in Zero Duplicate Orders.
Respect the rate limit instead of fighting it
When an API answers 429 with a Retry-After header, the standard retry strategy waits exactly that long before trying again, rather than guessing. For large batch jobs, though, the real fix is to stop hitting the limit in the first place – pace your requests or use the provider’s bulk endpoints, as we did to update 50,000 Shopify products without hitting the rate limit.
Make failures visible
Resilience should not mean silence. The pipeline reports retries, timeouts and circuit breaker state changes through the standard .NET logging and metrics, so a carrier that is quietly failing a third of its calls shows up on a dashboard before it shows up in customer complaints. Send that telemetry to Application Insights or Azure Log Analytics and alert when a circuit opens.
The payoff
- Slow APIs cost seconds, not minutes – every call has an attempt timeout and a hard overall limit.
- Transient blips are absorbed by retries the user never notices.
- Outages fail fast and fall back, instead of piling up threads and timeouts across your own system.
- No duplicate shipments or charges, because unsafe requests are only retried when an idempotency key makes it safe.
Takeaway
A resilient integration is mostly good configuration, not heroics: time out early, retry only what is safe to retry, back off politely, and stop calling a service that is down. With AddStandardResilienceHandler that is a few lines per client, and your application keeps working while the systems around it have a bad day. We design and build API integrations for payments, shipping, ERPs and e-commerce platforms that are made to survive exactly these failures – get in touch if a flaky third-party API is hurting your business.