Get your head around Header in AL Web Service Calls

Headers for HTTP calls are very important, but HTTP headers can also be quite confusing in AL, in this video, I show how it’s all connected, check it out:

https://youtu.be/QmCXUm0bEfU


In this video, Erik walks through the often-confusing world of HTTP headers when making web service calls from AL in Business Central. If you’ve ever tried to set a Content-Type header and received a cryptic .NET error, this one’s for you. Erik explains the critical distinction between request headers and content headers, why they must be applied in different places, and how to properly work with both.

Starting Point: A Simple HTTP POST

Erik begins with a piece of code from an earlier video — a simple web service call that posts data to PostTestServer v2, a handy tool that lets you inspect the exact request your code sent. Here’s the starting code:

procedure CallPstv2()
var
    Client: HttpClient;
    RequestMessage: HttpRequestMessage;
    ResponseMessage: HttpResponseMessage;
    Content: HttpContent;
    JsonBody: Text;
begin
    RequestMessage.Method('POST');
    RequestMessage.SetRequestUri('https://ptsv2.com/t/youtube/post');

    JsonBody := '{"zest": "123"}';
    Content.WriteFrom(JsonBody);
    RequestMessage.Content := Content;

    Client.Send(RequestMessage, ResponseMessage);
end;

Even though no headers are explicitly set in this code, when Erik inspects the result on the test server, nine headers appear. These are automatically added by the .NET HTTP stack underneath Business Central’s AL runtime. Notably, the Content-Type is set to text/plain — which is a problem if the receiving API expects JSON.

The First Attempt: Adding Content-Type to Request Headers

The natural instinct is to use HttpRequestMessage.GetHeaders() to retrieve the headers object, then add the Content-Type header there:

var
    Headers: HttpHeaders;
begin
    RequestMessage.GetHeaders(Headers);
    Headers.Add('Content-Type', 'application/json; charset=utf-8');
end;

But this immediately throws a .NET exception:

System.InvalidOperationException: Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.

This is a raw .NET exception bubbling up through AL — a reminder of how thin the wrapper is between AL and the underlying .NET HTTP library.

The Second Attempt: TryAddWithoutValidation

Erik tries using Headers.TryAddWithoutValidation('Content-Type', 'application/json') instead. This call doesn’t throw an error, but when inspecting the result on the test server, the Content-Type is still text/plain. The header was silently ignored because it was being applied to the wrong object.

The Correct Approach: Content Headers

The key insight is that Content-Type is a content header, not a request header. It must be set on the HttpContent object, not the HttpRequestMessage. Here’s how to do it properly:

var
    Headers: HttpHeaders;
begin
    Content.GetHeaders(Headers);
    Headers.Remove('Content-Type');
    Headers.Add('Content-Type', 'application/json; charset=utf-8');
end;

Note the call to Headers.Remove('Content-Type') before adding the new value. This is necessary because the content object already has a default Content-Type of text/plain, and this header does not support multiple values. Without the remove, you’ll get another error:

Cannot add value because header Content-Type does not support multiple values.

After this fix, the test server correctly shows application/json as the content type.

Understanding the .NET Header Classification

The reason for this behavior comes directly from .NET’s HTTP implementation. In the .NET reference source, there is an explicit list of headers classified as content headers:

  • Allow
  • Content-Encoding
  • Content-Language
  • Content-Length
  • Content-Location
  • Content-MD5
  • Content-Range
  • Content-Type
  • Expires
  • Last-Modified

These headers can only be used with HttpContent objects. All other headers (like Authorization, custom headers, etc.) belong on the request or response message. Because AL’s HTTP types are thin wrappers around these .NET types, they behave identically — including the same errors and constraints.

Adding Request Headers

For non-content headers, using RequestMessage.GetHeaders() works perfectly:

var
    Headers: HttpHeaders;
begin
    RequestMessage.GetHeaders(Headers);
    Headers.Add('Authorization', 'Bearer my-token-here');
    Headers.Add('X-Eriks-Header', 'some-custom-value');
end;

An important thing to understand about how the headers variable works: when you call GetHeaders(Headers), the variable you receive is essentially a pointer to the headers collection inside the request (or content) object. There is no corresponding SetHeaders method — you don’t need one. Any modifications you make to the Headers variable are immediately reflected in the parent object. This is somewhat un-AL-like behavior and is straight from .NET’s reference-type semantics.

Reading Response Headers

Headers also exist on the response side. When you receive a response, you can read headers that the server sent back:

var
    Headers: HttpHeaders;
    Values: array[10] of Text;
begin
    ResponseMessage.Headers(Headers);
    Headers.GetValues('Some-Header-Name', Values);
end;

This is useful when APIs return metadata in response headers, such as pagination tokens, rate limit information, or correlation IDs.

Bonus Tip: Avoid Heavy Processing in OnOpenPage

Erik shares an important side note about the OnOpenPage trigger, which he was using to fire the HTTP call for demonstration purposes. The OnOpenPage trigger fires before the UI is initialized, meaning:

  • Users won’t see anything on screen while it runs
  • You can’t show a progress dialog
  • The trigger can fire multiple times when the debugger is attached

The recommendation is to encapsulate data processing in a separate object and open the page with pre-loaded data, giving you the opportunity to show dialogs or progress indicators.

Summary

When working with HTTP headers in AL for Business Central, remember these key points:

  1. Request headers (like Authorization, custom headers) go on the HttpRequestMessage — use RequestMessage.GetHeaders()
  2. Content headers (like Content-Type, Content-Encoding) go on the HttpContent — use Content.GetHeaders()
  3. When setting Content-Type, remove the existing default value first before adding your own
  4. The Headers variable is a reference (pointer) to the headers collection — changes are applied immediately without needing a “set” method
  5. AL’s HTTP types are thin wrappers around .NET, so you may occasionally see raw .NET exceptions — reading them carefully often reveals the solution

This is a common stumbling block for AL developers working with web services, largely because the Business Central documentation doesn’t explicitly list which headers are classified as content headers versus request headers. Keeping the .NET reference source in mind can save you significant debugging time.