Let's have a simple example of an external service that works with Customers – and when a customer is changed there, the service can call a webhook and send a simple JSON body like this:
{
"id": 1,
"no": "01121212",
"name": "Spotsmeyer's Furnishings"
}
The goal is to receive the webhook in Business Central and process it (for example make some changes in Business Central data).
With a Custom API Page solution you won't need a third-party service, that will "translate" the webhook call for Business Central – you can directly call Business Central API. However:
You have two basic ways how to create an API page to receive webhooks – use a function or deep insert into Webhook Entries table.
As a rule of thumb: If you do not need multi-level JSON (no nested entities), stick with the function option, it is a bit easier to implement and returns HTTP status 200, which is usually what the external service wants.
First create a new API Page in your extension. With our example it can look like this:
page 4075362 "Webhook Test API RWU"
{
//Define the endpoint path in Business Central API
APIGroup = 'rwuTest';
APIPublisher = 'rwuCore';
APIVersion = 'v1.0';
//Basic API page parameters that for example define the entity name in API
Caption = 'webHookTestAPI';
DelayedInsert = true;
EntityName = 'testWebhook';
EntitySetName = 'testWebhooks';
PageType = API;
//Use "API Endpoint RWU" as a source table and "SystemId" as a main key field
SourceTable = "API Endpoint RWU";
ODataKeyFields = SystemID;
ChangeTrackingAllowed = true;
//You do not want to allow any modifications of "API Endpoint RWU" table (webhook definition)
//Data will be in fact inserted into "API Webhook Entry RWU" table
InsertAllowed = false;
ModifyAllowed = false;
DeleteAllowed = false;
//You need at least SystemId field in the API definition
layout
{
area(content)
{
repeater(General)
{
field(systemId; Rec.SystemId)
{
ApplicationArea = All;
Caption = 'systemId', Locked = true;
Editable = false;
}
}
}
}
//Create a ServiceEnabled function to receive the webhook
//Each parameter means a node in the request JSON and must have an appropriate data type
[ServiceEnabled]
procedure testUpdateCustomer(id: Integer; no: Text; name: Text): Text
var
APIScriptRWU: Codeunit "API Script RWU";
JsonObj: JsonObject;
PayloadBody: Text;
ResponseBody: Text;
begin
//You can't get the request JSON with a single command
//So you basically need to recreate it based on the function parameters
JsonObj.Add('id', id);
JsonObj.Add('no', no);
JsonObj.Add('name', name);
JsonObj.WriteTo(PayloadBody);
//Call RESTwithUS function to handle the webhook call
//This will create a new Webhook Entry for current webhook call
APIScriptRWU.HANDLE_WEBHOOK_CALL(Rec,PayloadBody,ResponseBody,false);
//Note: Without any response body, the request returns HTTP code 204
exit(ResponseBody);
end;
}
If you publish this page to your Business Central installation, you should now be able to receive webhooks with a proper URL. But there are some extra steps to make your life easier and let RESTwithUS create the URL for you.
First create a new codeunit that implements IWebHook RWU
interface. The GetWebHookPageMetadata
function provides some basic information about your webhook implementation. Fill in right _PageID and _FunctionName here.
codeunit 4075368 "Webhook Test Function RWU" implements "IWebHook RWU"
{
procedure GetWebHookPageMetadata(var _PageID: Integer; var _DeepInsertEntitySetName: Text; var _FunctionName: Text)
begin
_PageID := PAGE::"Webhook Test API RWU";
_DeepInsertEntitySetName := '';
_FunctionName := 'testUpdateCustomer';
end;
}
Second add your webhook implementation into WebHook ID RWU
enumerator. In the Implementation
part use the codeunit created in the previous step.
enumextension 4075363 "Test Webhook ID RWU" extends "WebHook ID RWU"
{
value(4075361; "Test Custom API Page Function")
{
Caption = 'Test Custom API Page Function';
Implementation = "IWebHook RWU" = "Webhook Test Function RWU";
}
}
You can now skip to Webhook Setup to create the webhook definition in RESTwithUS.
With the deep insert option you need to create two new pages. Let's first create a subpage, that will actually insert new records into Webhook Entries. The code looks as follows:
page 4075363 "Webhook Test API Entry RWU"
{
//Create a simple ListPart page, with "API Webhook Entry RWU" as a SourceTable
Caption = 'webhookTestAPIEntries';
DelayedInsert = true;
PageType = ListPart;
SourceTable = "API WebHook Entry RWU";
ODataKeyFields = SystemID;
PopulateAllFields = true;
//Define all fields of the request JSON
//If you need nested JSON entity, create another page and include it as subpart
layout
{
area(content)
{
repeater(General)
{
field(systemId; Rec.SystemId)
{
ApplicationArea = All;
Caption = 'systemId', Locked = true;
}
field(id; IdNode)
{
ApplicationArea = All;
Caption = 'id', Locked = true;
}
field(no; NoNode)
{
ApplicationArea = All;
Caption = 'no', Locked = true;
}
field(name; NameNode)
{
ApplicationArea = All;
Caption = 'id', Locked = true;
}
}
}
}
trigger OnInsertRecord(BelowxRec: Boolean): Boolean
begin
//Compose JSON for the webhook entry
JsonObj.Add('id', IdNode);
JsonObj.Add('no', NoNode);
JsonObj.Add('name', NameNode);
JsonObj.WriteTo(PayloadBody);
//Handle the webhook
APIScriptRWU.HANDLE_WEBHOOK_CALL(Rec,PayloadBody,ResponseBody,false);
end;
var
APIScriptRWU: Codeunit "API Script RWU";
JsonObj: JsonObject;
PayloadBody: Text;
ResponseBody: Text;
IdNode: Integer;
NoNode: Text;
NameNode: Text;
}
Now create a new API page, that will use the ListPart for JSON payload definition and deep insert into Webhook Entries:
page 4075362 "Webhook Test API RWU"
{
//Define the endpoint path in BC API
APIGroup = 'rwuTest';
APIPublisher = 'rwuCore';
APIVersion = 'v1.0';
//Some basic API page parameters
Caption = 'webHookTestAPI';
DelayedInsert = true;
EntityName = 'testWebhook';
EntitySetName = 'testWebhooks';
PageType = API;
//Use "API Endpoint RWU" as a source table and "SystemId" as a main key field
SourceTable = "API Endpoint RWU";
ODataKeyFields = SystemID;
ChangeTrackingAllowed = true;
//You do not want to allow any modifications of "API Endpoint RWU" (webhook definition)
//Data will be in fact inserted into "API Webhook Entry RWU" table
InsertAllowed = false;
ModifyAllowed = false;
DeleteAllowed = false;
//You need at least SystemId field in the API definition
layout
{
area(content)
{
repeater(General)
{
field(systemId; Rec.SystemId)
{
ApplicationArea = All;
Caption = 'systemId', Locked = true;
Editable = false;
}
//Use the page created in previous step as a subpart for deep insert
part(webhookEntries;"WebHook Test API Entry RWU")
{
ApplicationArea = All;
Caption = 'webhookAPI';
EntityName = 'webhookEntry';
EntitySetName = 'webhookEntries';
SubPageLink = "Endpoint System Id" = FIELD(SystemID);
}
}
}
}
}
If you publish these pages to your Business Central installation, you should now be able to receive webhooks with a proper URL. But there are some extra steps to make your life easier and let RESTwithUS create the URL for you.
First create a new codeunit that implements IWebHook RWU
interface. The GetWebHookPageMetadata
function provides some basic information about your webhook implementation. Fill in right _PageID and _DeepInsertEntitySetName (this should be the EntitySetName of the subpart page).
codeunit 4075367 "Webhook Test Page RWU" implements "IWebHook RWU"
{
procedure GetWebHookPageMetadata(var _PageID: Integer; var _DeepInsertEntitySetName: Text; var _FunctionName: Text)
begin
_PageID := PAGE::"Webhook Test API RWU";
_DeepInsertEntitySetName := 'webhookEntries';
_FunctionName := '';
end;
}
Second add your webhook implementation into WebHook ID RWU
enumerator. In the Implementation
part use the codeunit created in the previous step.
enumextension 4075363 "Test Webhook ID RWU" extends "WebHook ID RWU"
{
value(4075360; "Test Custom API Page")
{
Caption = 'Test Custom API Page';
Implementation = "IWebHook RWU" = "Webhook Test Page RWU";
}
}
To create a new webhook select your API provider from the list and open Webhooks:
Name your webhook as you see fit in the Description field. In the Webhook ID field choose one of your custom options. If you did everything correctly, RESTwithUS should autogenerate the Endpoint URL based on the API page and enumerator definitions.
Tip: You can always fill in the endpoint URL by yourself – and you may need to in case of Business Central 14 installations, where you can use OData webservices.
WebHook ID RWU
enumerator properly, it will autogenerate the Endpoint URL and Processing API Page ID for you.{"Status":"200 OK"}
. Received JSON data are not processed immediately, but the job is scheduled in Job Queue and will start asynchronously in a few seconds. For more details see guideline Processing webhooks.Open
, meaning you can update the webhook definition.As you can see, the webhook URL is quite complicated, so let's see, what the different parts mean for different URLs:
The logic goes as following:
http://restwithus-18:7048/BC/api
– Basic address of your Business Central API (must be allowed for on-prem installations). Depends on your Business Central installation./rwuCore/rwuTest/v1.0
– API endpoint identification. Depends on the APIGroup, APIPublisher and APIVersion settings of your API page./companies(74e35bd0-2590-eb11-bb66-000d3abcddd1)
– Select right company in Business Central. This part is not needed if you are querying the default company./testWebhooks(2180b92e-b422-ec11-83e3-e4682e856966)
– Open the right entity. Depends on EntitySetName settings of your API page. Note that the GUID is different for both options – it is the GUID of the webhook definition and it tells RESTwithUS, where to create the webhook entry./Microsoft.NAV.testUpdateCustomer
– Name of the API page function called externally./webhookEntries
– Name of the deep insert entity. Depends on the EntitySetName settings in the subpart definition.Let's now try to call the webhook endpoint from Postman (the webhook must be Released and you must be able to access Business Central API):
Please note a few details:
POST
and you are calling the endpoint URL from webhook list in RESTwithUS. (In this example it's the Test Webhook Custom Function endpoint).Each webhook call creates a new Webhook Entry. To view the list of those entries select your webhook from the list and open Webhook Entries:
Asynchronous
, the Status will be Scheduled
at first.Processed
webhooks were already processed by RESTwithUS and a required action was taken. (See guideline Processing webhooks.)Error
and you will see an error message in Error Message field.Of course this is not the end. You just registered a webhook call in Business Central and you need to process the data somehow. For more details see guideline Processing webhooks.