In this series I’m building a Microsoft Dynamics 365 Business Central extension that talks with the US Postal Service for doing address verification.

Episode 1, create the web service call:
Stay tuned for the next episodes where I continue building this extension.
When the extension is done, the source will be available on github.com
In this first part of a video series, Erik walks through the fundamentals of consuming external web services from Business Central AL code. He uses the US Postal Service Address Validation API as a practical example, building out the core HTTP client infrastructure, a setup table, and all the plumbing needed to make an outbound web service call.
Choosing a Web Service: USPS Address Validation
The web service Erik selected for this exercise is the US Postal Service Address Validation API. It’s a straightforward API where you send an address and receive either a confirmation or the corrected address back. The API follows a simple pattern: you make an HTTP GET request to a URL like https://secure.shippingapis.com/ShippingAPI.dll with a query string containing two parameters: API (the name of the API you’re calling) and XML (the XML payload with the address details).
This pattern is consistent across multiple USPS APIs (address validation, zip code lookup, etc.), which means the core calling mechanism can be reused if the extension is expanded later.
Setting Up the Project Structure
Erik starts with a Visual Studio Code workspace already connected to a Business Central instance. The approach is to create a codeunit that serves as the central management point for all web service communication.
The Setup Table and Page
Before writing the web service call, we need somewhere to store configuration — specifically the base URL and a User ID (which you get by registering with the USPS web services). Erik creates a single-record setup table:
table 56102 "US Postal Service Setup"
{
Caption = 'US Postal Service Setup';
DataClassification = ToBeClassified;
fields
{
field(1; "Primary Key"; Code[10])
{
Caption = 'Primary Key';
DataClassification = SystemMetadata;
}
field(2; URL; Text[250])
{
Caption = 'URL';
DataClassification = SystemMetadata;
}
field(3; "User ID"; Text[50])
{
Caption = 'User ID';
DataClassification = SystemMetadata;
}
}
keys
{
key(PK; "Primary Key")
{
Clustered = true;
}
}
}
A card page is created for this setup table. One important difference Erik highlights between NAV and Business Central is the handling of single-record tables. In NAV, there was an implicit insert when opening a card page for these tables. In Business Central, that behavior is gone, so you need to handle it explicitly:
trigger OnOpenPage()
begin
if Rec.IsEmpty then
Rec.Insert();
end;
Without this, users would try to type into the fields and nothing would happen — they’d have to manually click the “+” to insert a record first. It’s a small thing, but important for a good user experience.
Building the Web Service Call
The heart of this video is the CallWebService procedure in the management codeunit. Here’s the full function signature and logic Erik builds out:
codeunit 56101 "US Postal Service Management"
{
procedure CallWebService(API: Text; XMLIn: XmlDocument): XmlDocument
var
Setup: Record "US Postal Service Setup";
Client: HttpClient;
Request: HttpRequestMessage;
Response: HttpResponseMessage;
Headers: HttpHeaders;
QueryString: Text;
XMLText: Text;
TxtOut: Text;
XMLOut: XmlDocument;
TypeHelper: Codeunit "Type Helper";
begin
if not Setup.Get() then
Error('US Postal Service setup is needed. Please enter URL and User ID.');
// Transform XML document to text and URL-encode it
XMLIn.WriteTo(XMLText);
TypeHelper.UrlEncode(XMLText);
// Build the query string
QueryString := '?API=' + API + '&XML=' + XMLText;
// Set up the HTTP request
Request.Method('GET');
Request.SetRequestUri(Setup.URL + QueryString);
// Set headers
Request.GetHeaders(Headers);
Headers.Add('User-Agent', 'Dynamics 365 Business Central');
// Make the call
if not Client.Send(Request, Response) then
Error('Cannot contact US Postal Service. Network error.');
// Check HTTP status code
if Response.HttpStatusCode() <> 200 then
Error('Web service call failed. Status code: %1', Response.HttpStatusCode());
// Read the response content
Response.Content.ReadAs(TxtOut);
// Parse the response XML
if XmlDocument.ReadFrom(TxtOut, XMLOut) then
exit(XMLOut)
else
Error('Expected XML format from US Postal Service. Got this instead: %1', TxtOut);
end;
}
Key Concepts Explained
The HttpClient Pattern
The central piece of this function is the Client.Send(Request, Response) statement. This is where we send an HTTP request to the web service and receive a response. The pattern involves three core types:
- HttpClient — The client that handles sending the request
- HttpRequestMessage — The request we’re building and sending
- HttpResponseMessage — The response we receive back
URL Encoding with Type Helper
Since the XML payload is passed as part of the URL query string, it must be URL-encoded. Special characters like spaces, angle brackets, and ampersands would break the URL if left as-is. The Type Helper codeunit provides a UrlEncode method that converts these characters into their percent-encoded equivalents (e.g., a space becomes %20).
Erik notes an interesting quirk of this function: if you press F12 to inspect it, you’ll see it takes the value by reference and also returns the text. So it encodes the variable in place and returns the result. Be careful not to accidentally double-encode by doing something like XMLText := TypeHelper.UrlEncode(XMLText) — that would encode it twice.
Setting HTTP Headers
While not strictly required, Erik recommends setting a User-Agent header on your requests. This is the header that identifies who is making the call — normally it would be a browser name like “Chrome” or “Firefox.” Setting it to “Dynamics 365 Business Central” is proper etiquette and helps with debugging on the server side when someone reviews access logs.
New AL Data Types and Return Values
Erik highlights several features in AL that weren’t available in the old NAV C/AL environment:
- XmlDocument as a return type — In NAV, you couldn’t use complex data types as return values from functions. In AL, you can return
XmlDocument, making the API much cleaner. - Static methods on types —
XmlDocument.ReadFrom()is called directly on the type, not on an instance. This is similar to static methods in C#. - Fluent dot notation — You can chain calls like
Response.Content.ReadAs(TxtOut)without needing intermediate variables.
Error Handling Strategy
The function includes three layers of error handling:
- Network errors — If
Client.Send()returnsfalse, there’s no network connection or the server couldn’t be reached at all. - HTTP status codes — If the response status code isn’t 200, the call reached the server but something went wrong (404 Not Found, 403 Forbidden, etc.).
- XML parsing — Even with a 200 response, the content might not be valid XML. The
ReadFromfunction returns a boolean indicating whether the parse succeeded.
Erik also mentions that there are additional checks you could perform (such as checking for a 403 Forbidden status, which would indicate you’re not authorized), but the status code check covers the general case well enough.
Summary
In this first video of the series, Erik built the foundational infrastructure for calling external web services from Business Central AL:
- A setup table and page to store the USPS API URL and User ID
- A management codeunit with a generic
CallWebServicefunction - Proper HTTP request construction including method, URI, query string, and headers
- URL encoding of the XML payload using the Type Helper codeunit
- Response handling with multiple layers of error checking
- XML parsing of the response into an XmlDocument for further processing
The function is designed to be reusable — by accepting the API name and XML document as parameters, it can support any of the USPS shipping APIs, not just address validation. In Part 2, Erik will focus on building the actual XML request payload and parsing the response to make this function fully operational.