Skip to content

Business Central + Azure Functions: Building a Reliable API Integration with AL and C#

Technical tutorial · AL · C# · Azure

A successful HTTP request is not the same thing as a reliable integration. This article explores a practical architecture for connecting Microsoft Dynamics 365 Business Central to Azure Functions, with secure authentication, error handling, idempotency and monitoring. The example is generic and contains no client information.

1. Architecture: separate responsibilities

Business Central (AL)
  → HTTPS API
Azure Function (.NET isolated worker)
  → Validate → Check persistent Idempotency-Key
  → Persist message / enqueue job
Worker → Target system
  → Processing status + Correlation ID

For long-running work, use a durable queue. Return HTTP 202 only after the work has been safely accepted and stored. A 202 response does not mean the business transaction is complete.

2. Define a stable API contract

POST /api/orders
Content-Type: application/json
Idempotency-Key: tenant1-order-10042-v1
x-functions-key: <secret>

{"documentNo":"10042","amount":199.95,"currency":"EUR"}

The idempotency key should identify a unique business operation, including tenant, environment, document and version where relevant. Store it alongside a request hash, processing state and result. Use an atomic insert or unique constraint to prevent race conditions.

3. Azure Function in C#

This .NET isolated-worker example illustrates request validation. It is a learning sample, not production-ready persistence logic.

using System.Net;
using System.Text.Json;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;

public record OrderDto(string DocumentNo, decimal Amount);
public class Orders
{
    [Function("Orders")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Function,
          "post", Route = "orders")] HttpRequestData req)
    {
        OrderDto? data;
        try {
            data = await JsonSerializer.DeserializeAsync<OrderDto>(
                req.Body, new JsonSerializerOptions
                { PropertyNameCaseInsensitive = true });
        } catch (JsonException) {
            return req.CreateResponse(HttpStatusCode.BadRequest);
        }
        if (data is null || string.IsNullOrWhiteSpace(data.DocumentNo))
            return req.CreateResponse(HttpStatusCode.BadRequest);

        // TODO: atomically persist idempotency state
        // and enqueue work BEFORE returning 202.
        return req.CreateResponse(HttpStatusCode.Accepted);
    }
}

A Function key alone is not a substitute for comprehensive security. Consider Entra ID, API Management and restrictive network access for sensitive integrations.

4. Handling HTTP in AL

The AL client must distinguish a transport failure from a non-success HTTP response:

codeunit 50120 "HTTP Connection Demo"
{
    procedure CheckService()
    var
        Client: HttpClient;
        Response: HttpResponseMessage;
    begin
        // Simple connectivity example.
        // A real POST also needs authentication,
        // content, and an idempotency key.
        if not Client.Get('https://example.com/health', Response) then
            Error('HTTP transport failed.');

        if not Response.IsSuccessStatusCode() then
            Error('Service returned HTTP %1.', Response.HttpStatusCode());
    end;
}

Outbound HttpClient calls are disabled by default for Business Central extensions. Enable Allow HttpClient Requests in Extension Settings. Never hardcode credentials in published AL examples.

5. Retry policy and uncertain outcomes

  • 400/422: fix input data rather than automatically retry.
  • 401/403: investigate authentication and authorization.
  • 409: review idempotency or version conflicts.
  • 429/503: bounded retries with backoff, jitter and Retry-After.
  • Timeout: the target might already have accepted the operation. Reuse the same key or query its status.

6. Observability and go-live checklist

Log a correlation ID, technical error category, processing state and duration, but never secrets or unnecessary personal information. Test duplicate requests, malformed JSON, timeouts after acceptance, rate limits and downstream downtime. A reliable integration is defined by predictable recovery behavior, not just a successful demo request.

Official documentation

Walkthrough: From a Business Central document to completed processing

Consider a posted sales document that must be transferred from Business Central to another system. The first architectural decision is to treat the posting in Business Central and the processing in the destination as separate operations. They do not share a database transaction. Therefore, the integration needs a durable record of what should be sent, what has been accepted and what has actually been completed.

  1. Prepare the payload: Collect the required business data and validate mandatory fields before submitting the request.
  2. Choose a stable operation key: Reuse the same idempotency key for retries of the same business operation; use a new key for a genuinely new version.
  3. Accept durably: The Azure Function validates the request, atomically reserves the key and persists or queues the operation.
  4. Return an acknowledgment: Only after durable acceptance should the API respond with HTTP 202 and an operation identifier.
  5. Process and reconcile: A worker handles the downstream call. Business Central polls a secured status endpoint or consumes an authenticated callback to learn the final outcome.

Understanding 202 Accepted

HTTP 202 acknowledges acceptance for processing; it does not prove the downstream transaction is complete. Design a status endpoint such as GET /api/operations/{operationId}. A useful response contains the operation ID, its current status, last update timestamp and a safe technical error code where necessary. Avoid exposing secrets or sensitive business fields in publicly reachable status responses.

Making idempotency survive concurrency

Imagine two Business Central job queue sessions issuing the same request at almost the same moment. Reading “the record does not exist” and then inserting it is not sufficient: both workers could read the same absence before either inserts. The operation reservation must be atomic, usually enforced through a unique database constraint. Store a normalized request hash with the key. If a subsequent request reuses the key with a different payload, reject it as a conflict rather than silently accepting altered data.

Repeated requests with the same key and payload should resolve to the existing operation and processing state. This makes it safe to repeat a request after a connection timeout that occurred after the server accepted the work. Keep in mind that input idempotency does not automatically ensure exactly-once effects in the target system. The worker also needs a recovery strategy, and the downstream system should ideally support a stable business identifier.

Handling failures in AL without endless retries

The short AL example above raises an error immediately. That can be appropriate for a demonstration but is not a complete background-processing strategy. A durable integration should classify failures and record them in an integration ledger or equivalent tracking mechanism.

  • Transport failure: DNS issues, outbound-call restrictions and timeouts may prevent a usable HTTP response. The downstream outcome can be uncertain.
  • 4xx responses: Validation or permissions usually require a correction rather than immediate retry.
  • 429 and transient 5xx responses: Consider bounded retries with exponential backoff, jitter and applicable Retry-After guidance.

Use explicit timeouts, concurrency limits and configurable endpoints. Do not include API keys, access tokens or other credentials in source code or log output. Review the Business Central extension setting that permits outbound HTTP requests.

Authentication and authorization are separate concerns

A Function key is a shared secret, not a complete authorization model. For sensitive APIs, prefer a design with Microsoft Entra ID access tokens or an appropriately secured API gateway. Validate the token signature, issuer, audience and required scopes or app roles. Limit permissions to the operations the calling application actually needs. Network restrictions and API Management can provide additional protection when appropriate.

Observability across the entire request path

Pass a correlation ID through Business Central, the Function, the processing queue and the destination adapter. Monitor response times, failure counts and queue age. In Business Central, expose enough state for operators to see which documents are queued, completed, blocked by validation or awaiting manual attention. Separate business errors from temporary infrastructure failures so the recovery procedure is clear.

Go-live test matrix

  1. Submit two identical requests concurrently; only one business operation should be created.
  2. Terminate the HTTP connection after the operation is stored, then retry with the original key.
  3. Reuse a key with a different request body and verify an explicit conflict response.
  4. Submit malformed payloads and requests without adequate permissions.
  5. Make the destination unavailable and confirm work remains recoverable.
  6. Review telemetry for accidental exposure of credentials or personal data.

Implementation boundary: The C# example earlier in this article is intentionally a validation-only learning sample. It does not implement the persistence, queue, authentication and status-management layers described here. Do not deploy that snippet as a complete production integration without adding those components and testing them.

Discussion (0)

No comments yet. Be the first to contribute.

Leave a comment