Use Webhooks to create external handlers for server events
With Microsoft Dataverse, you can send data about events that occur on the server to a web application using webhooks. Webhooks is a lightweight HTTP pattern for connecting Web APIs and services with a publish/subscribe model. Webhook senders notify receivers about events by making requests to receiver endpoints with some information about the events.
Webhooks enable developers and ISV's to integrate Dataverse data with their own custom code hosted on external services. By using the WebHook model, you can secure your endpoint by using authentication header or query string parameter keys. This is simpler than the SAS authentication model that you may currently use for Azure Service Bus integration.
When deciding between the WebHook model and the Azure Service Bus integration, here are some items to keep in mind:
- Azure Service Bus works for high scale processing, and provides a full queueing mechanism if Dataverse is pushing many events.
- Webhooks can only scale to the point at which your hosted web service can handle the messages.
- Webhooks enables synchronous and asynchronous steps. Azure Service Bus only allows for asynchronous steps.
- Webhooks send POST requests with JSON payload and can be consumed by any programming language or web application hosted anywhere.
- Both Webhooks and Azure Service Bus can be invoked from a plug-in or custom workflow activity.
Get Started
There are three parts to using Webhooks:
- Creating or configuring a service to consume WebHook requests.
- Registering WebHook step on the Dataverse service, or
- Invoking a WebHook from a plug-in or custom workflow activity.
Start by registering a test WebHook
In order to understand how to create and configure a service to consume a WebHook request from Dataverse, it is valuable to start by understanding how to register a WebHook. More information: Register a WebHook
When you have registered an example WebHook you can use a request logging site to examine the contextual data that will be passed. More information: Test WebHook registration with request logging site
Tip
Completing the steps to register a test WebHook and examining the contextual data that is passed will help make the rest of the information in this topic easier to understand. Complete these steps and return to this topic.
Create or configure a service to consume WebHook requests
Webhooks are simply a pattern that can be applied using a wide range of technologies. There are no required frameworks, platforms, or programming languages you must use. Use the skills and knowledge you have to deliver the appropriate solution.
Azure Functions provide an excellent way to deliver a solution using Webhooks, but it is not a requirement. This section will not provide guidance towards a specific solution but will instead describe the data that will be passed to your service that will enable your service to add value.
As demonstrated in Test WebHook registration with request logging site, you can register a test WebHook step and use the request logging site to capture the specific kinds of data that your application can process.
Data passed to the service
There are three types of data in the request: Query String, Header Data, and Request Body.
Query String
The only kind of data that will be passed as a query string may be the authentication values passed if the WebHook is configured to use the WebhookKey or HttpQueryString options as described in Authentication options.
Header Data
If you choose the HttpHeader authentication option, you will need to use the key/value pairs that your service will require.
Other data you can expect to find passed to your service is in the table below:
Key | Value Description |
---|---|
x-ms-dynamics-organization |
The domain name of the environment sending the request |
x-ms-dynamics-entity-name |
The logical name of the table passed in the execution context data. |
x-ms-dynamics-request-name |
The name of the event that the WebHook step was registered for. |
x-ms-correlation-request-id |
Unique identifier for tracking any type of extension. This property is used by the platform for infinite loop prevention. In most cases, this property can be ignored. This value may be used when working with technical support because it can be used to query telemetry to understand what occurred during the entire operation. |
x-ms-dynamics-msg-size-exceeded |
Sent only when the HTTP payload size exceeds the 256KB. |
Request Body
The body will contain string that represents the JSON value of an instance of the RemoteExecutionContext class. This is the same data that is passed to Azure Service Bus integrations.
The service you create must parse this data to extract the relevant items of information for your service to provide its function. How you choose to parse this data depends on the technology you are using and your preferences.
The following is an example of the serialized JSON data passed for a step registered with the following properties:
Property | Description |
---|---|
Message | Update |
Primary Entity | contact |
Secondary Entity | none |
Filtering Attributes | firstname,lastname |
Run in User's Context | Calling User |
Execution Order | 1 |
Event Pipeline Stage of Execution | PostOperation |
Execution Mode | Asynchronous |
{
"BusinessUnitId": "e2b9dd85-e89e-e711-8122-000d3aa2331c",
"CorrelationId": "aaaa0000-bb11-2222-33cc-444444dddddd",
"Depth": 1,
"InitiatingUserId": "11bb11bb-cc22-dd33-ee44-55ff55ff55ff",
"InputParameters": [{
"key": "Target",
"value": {
"__type": "Entity:http:\/\/schemas.microsoft.com\/xrm\/2011\/Contracts",
"Attributes": [{
"key": "firstname",
"value": "James"
}, {
"key": "contactid",
"value": "6d81597f-0f9f-e711-8122-000d3aa2331c"
}, {
"key": "fullname",
"value": "James Glynn (sample)"
}, {
"key": "yomifullname",
"value": "James Glynn (sample)"
}, {
"key": "modifiedon",
"value": "\/Date(1506384247000)\/"
}, {
"key": "modifiedby",
"value": {
"__type": "EntityReference:http:\/\/schemas.microsoft.com\/xrm\/2011\/Contracts",
"Id": "11bb11bb-cc22-dd33-ee44-55ff55ff55ff",
"KeyAttributes": [],
"LogicalName": "systemuser",
"Name": null,
"RowVersion": null
}
}, {
"key": "modifiedonbehalfby",
"value": null
}],
"EntityState": null,
"FormattedValues": [],
"Id": "6d81597f-0f9f-e711-8122-000d3aa2331c",
"KeyAttributes": [],
"LogicalName": "contact",
"RelatedEntities": [],
"RowVersion": null
}
}],
"IsExecutingOffline": false,
"IsInTransaction": false,
"IsOfflinePlayback": false,
"IsolationMode": 1,
"MessageName": "Update",
"Mode": 1,
"OperationCreatedOn": "\/Date(1506409448000-0700)\/",
"OperationId": "4af10637-4ea2-e711-8122-000d3aa2331c",
"OrganizationId": "00aa00aa-bb11-cc22-dd33-44ee44ee44ee",
"OrganizationName": "OrgName",
"OutputParameters": [],
"OwningExtension": {
"Id": "75417616-4ea2-e711-8122-000d3aa2331c",
"KeyAttributes": [],
"LogicalName": "sdkmessageprocessingstep",
"Name": null,
"RowVersion": null
},
"ParentContext": {
"BusinessUnitId": "e2b9dd85-e89e-e711-8122-000d3aa2331c",
"CorrelationId": "aaaa0000-bb11-2222-33cc-444444dddddd",
"Depth": 1,
"InitiatingUserId": "11bb11bb-cc22-dd33-ee44-55ff55ff55ff",
"InputParameters": [{
"key": "Target",
"value": {
"__type": "Entity:http:\/\/schemas.microsoft.com\/xrm\/2011\/Contracts",
"Attributes": [{
"key": "firstname",
"value": "James"
}, {
"key": "contactid",
"value": "6d81597f-0f9f-e711-8122-000d3aa2331c"
}],
"EntityState": null,
"FormattedValues": [],
"Id": "6d81597f-0f9f-e711-8122-000d3aa2331c",
"KeyAttributes": [],
"LogicalName": "contact",
"RelatedEntities": [],
"RowVersion": null
}
}, {
"key": "SuppressDuplicateDetection",
"value": false
}],
"IsExecutingOffline": false,
"IsInTransaction": false,
"IsOfflinePlayback": false,
"IsolationMode": 1,
"MessageName": "Update",
"Mode": 1,
"OperationCreatedOn": "\/Date(1506409448000-0700)\/",
"OperationId": "4af10637-4ea2-e711-8122-000d3aa2331c",
"OrganizationId": "00aa00aa-bb11-cc22-dd33-44ee44ee44ee",
"OrganizationName": "OneFarm",
"OutputParameters": [],
"OwningExtension": {
"Id": "75417616-4ea2-e711-8122-000d3aa2331c",
"KeyAttributes": [],
"LogicalName": "sdkmessageprocessingstep",
"Name": null,
"RowVersion": null
},
"ParentContext": null,
"PostEntityImages": [],
"PreEntityImages": [],
"PrimaryEntityId": "6d81597f-0f9f-e711-8122-000d3aa2331c",
"PrimaryEntityName": "contact",
"RequestId": null,
"SecondaryEntityName": "none",
"SharedVariables": [{
"key": "ChangedEntityTypes",
"value": [{
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "feedback",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "contract",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "salesorder",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "connection",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "socialactivity",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "postfollow",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "incident",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "invoice",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "entitlement",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "lead",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "opportunity",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "quote",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "socialprofile",
"value": "Update"
}, {
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "contact",
"value": "Update"
}]
}],
"Stage": 30,
"UserId": "11bb11bb-cc22-dd33-ee44-55ff55ff55ff"
},
"PostEntityImages": [{
"key": "AsynchronousStepPrimaryName",
"value": {
"Attributes": [{
"key": "fullname",
"value": "James Glynn (sample)"
}, {
"key": "contactid",
"value": "6d81597f-0f9f-e711-8122-000d3aa2331c"
}],
"EntityState": null,
"FormattedValues": [],
"Id": "6d81597f-0f9f-e711-8122-000d3aa2331c",
"KeyAttributes": [],
"LogicalName": "contact",
"RelatedEntities": [],
"RowVersion": null
}
}],
"PreEntityImages": [],
"PrimaryEntityId": "6d81597f-0f9f-e711-8122-000d3aa2331c",
"PrimaryEntityName": "contact",
"RequestId": null,
"SecondaryEntityName": "none",
"SharedVariables": [],
"Stage": 40,
"UserId": "11bb11bb-cc22-dd33-ee44-55ff55ff55ff"
}
Important
When the size of the entire HTTP payload exceeds 256KB, the x-ms-dynamics-msg-size-exceeded
header will be included and the following RemoteExecutionContext properties will be removed:
Some operations do not include these properties.
Invoke a WebHook from a plug-in or workflow activity
Because a WebHook is a kind of service endpoint you can also invoke it without registering a step with a plug-in or workflow activity in the same way you can for an Azure Service Bus endpoint. You need to provide the ServiceEndpointId to the IServiceEndpointNotificationService interface. See the following Azure Service Bus samples for more information:
Troubleshoot WebHook registrations
Webhooks are relatively simple. The service will send the request and evaluate the response. The system cannot parse any data returned with the body of the response, it will only look at the response StatusCode
value.
The timeout is 60 seconds. Generally, if no response is returned before the timeout period or if the response StatusCode
value is not within the 2xx
range to indicate success it will fail. The exception is when the error returned is in the following table:
StatusCode | Description |
---|---|
502 |
Bad Gateway |
503 |
Service Unavailable |
504 |
Gateway Timeout |
These errors indicate a networking issue that might be resolved with another attempt. The WebHook service will make one more attempt only when these error codes are returned.
Asynchronous Webhooks
If your web hook is registered to run asynchronously, you can examine the System Job for details on the error. More information: Query failed asynchronous jobs for a given step
Synchronous Webhooks
When you choose to use a synchronous execution mode any failure will be reported back to the user of the application with an Endpoint unavailable error dialog informing the user that the webhook service endpoint may be configured incorrectly or is not available. The dialog will allow you to download a log file to get details on any errors.
Note
Any webhook registered for a synchronous step will send the execution context data to the configured endpoint immediately. If an error occurs after the request was sent, the data operation will rollback but the request sent to the configured endpoint cannot be recalled.
Next steps
Register a WebHook
Test WebHook registration with request logging site
See also
Write a plug-in
Register a plug-in
Asynchronous service in Dataverse
Sample: Azure aware custom plug-in
Sample: Azure aware custom workflow activity
Azure Functions
ServiceEndpoint table
SdkMessageProcessingStep table
AsynchronousOperations table
RemoteExecutionContext
IServiceEndpointNotificationService