Retry Logic for HttpClient in Business Central AL: Handling 429 and 503 Errors


When you call an external API from Business Central using HttpClient, the happy path is easy. The hard part starts when the API returns 429 Too Many Requests or 503 Service Unavailable. A single failed call can break a posting routine, leave a sales order half-synced, or throw a raw error at the user. This post shows a clean, production-ready retry pattern in AL with exponential backoff.

Why You Need Retry Logic

Most modern APIs (payment gateways, WhatsApp Cloud API, shipping providers, Azure services) enforce rate limits. When you exceed them, the server responds with HTTP 429 and often a Retry-After header. Transient failures like 503 and 504 are also common under load. Without retry logic, your integration is only as reliable as the busiest second on the remote server.

The Core Pattern

The idea is simple: wrap the send call in a loop, catch retryable status codes, wait a growing interval between attempts, and give up after a maximum number of tries.

codeunit 50120 "Http Retry Handler"
{
    procedure SendWithRetry(RequestMessage: HttpRequestMessage; var ResponseMessage: HttpResponseMessage): Boolean
    var
        Client: HttpClient;
        Attempt: Integer;
        MaxAttempts: Integer;
        WaitMs: Integer;
    begin
        MaxAttempts := 4;
        for Attempt := 1 to MaxAttempts do begin
            if Client.Send(RequestMessage, ResponseMessage) then
                if not IsRetryable(ResponseMessage.HttpStatusCode()) then
                    exit(ResponseMessage.IsSuccessStatusCode());

            // Exponential backoff: 1s, 2s, 4s, 8s
            WaitMs := 1000 * Power(2, Attempt - 1);
            if Attempt < MaxAttempts then
                Sleep(WaitMs);
        end;
        exit(false);
    end;

    local procedure IsRetryable(StatusCode: Integer): Boolean
    begin
        exit(StatusCode in [429, 500, 502, 503, 504]);
    end;
}

Respecting the Retry-After Header

Well-behaved APIs tell you exactly how long to wait through the Retry-After response header. When present, this value should override your calculated backoff. Ignoring it is the fastest way to get your integration throttled harder or temporarily blocked.

local procedure GetRetryAfterMs(ResponseMessage: HttpResponseMessage; DefaultMs: Integer): Integer
var
    Headers: HttpHeaders;
    Values: List of [Text];
    Seconds: Integer;
begin
    ResponseMessage.GetHeaders(Headers);
    if Headers.Contains('Retry-After') then begin
        Headers.GetValues('Retry-After', Values);
        if Evaluate(Seconds, Values.Get(1)) then
            exit(Seconds * 1000);
    end;
    exit(DefaultMs);
end;

Critical Warning: Never Sleep Inside a Posting Transaction

The Sleep() approach above works well for background jobs, job queue entries, and manual actions. It is dangerous inside a database transaction. Calling Sleep() while a posting routine holds locks will block other users and can trigger deadlocks across your company. For posting-time integrations, decouple the API call: write the payload to a queue table, commit, then process it through a separate job queue entry.

Testing Retry Behaviour

You cannot rely on a live API to reliably produce a 429 on demand. Extract an interface for the send operation and mock it in your test codeunit so you can force each status code deterministically. This keeps your retry logic under test coverage without hammering the real endpoint.

Key Takeaways

  • Retry only on transient status codes: 429, 500, 502, 503, 504. Never retry a 400 or 401, since those will fail every time.
  • Use exponential backoff so you do not stampede a recovering server.
  • Always honour Retry-After when the API provides it.
  • Keep Sleep() out of posting transactions to avoid locks and deadlocks.
  • Abstract the send call behind an interface so retry logic stays testable.

This pattern turns a fragile one-shot integration into one that survives real-world API conditions, which is exactly what production Business Central deployments demand.

Comments

Popular posts from this blog

IF - THEN And IF - THEN - ELSE Satement In Microsoft Dynamics NAV

WITH Loop / Statement

FOR Loop