Use webhooks to create external handlers for server events

With Common Data Service for Apps, 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 Customer Engagement 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 CDS for Apps 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 plugin or custom workflow activity.

Get Started

There are three parts to using web hooks:

  • Creating or configuring a service to consume webhook requests.
  • Registering webhook step on the CDS for Apps 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 CDS for apps, it is valuable to start by understanding how to register a web hook. More information: Register a web hook

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 is 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 web hook 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-request-id A unique identifier for the request
x-ms-dynamics-organization The name of the tenant sending the request
x-ms-dynamics-entity-name The logical name of the entity 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": "b374239d-4233-41a9-8b17-a86cb4f737b5",
    "Depth": 1,
    "InitiatingUserId": "75c2dd85-e89e-e711-8122-000d3aa2331c",
    "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": "75c2dd85-e89e-e711-8122-000d3aa2331c",
                    "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": "4ef5b371-e89e-e711-8122-000d3aa2331c",
    "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": "b374239d-4233-41a9-8b17-a86cb4f737b5",
        "Depth": 1,
        "InitiatingUserId": "75c2dd85-e89e-e711-8122-000d3aa2331c",
        "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": "4ef5b371-e89e-e711-8122-000d3aa2331c",
        "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": "75c2dd85-e89e-e711-8122-000d3aa2331c"
    },
    "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": "75c2dd85-e89e-e711-8122-000d3aa2331c"
}

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 plugin 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 web hook 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.

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 CDS for Apps
Sample: Azure aware custom plug-in
Sample: Azure aware custom workflow activity
Azure Functions
ServiceEndpoint Entity
SdkMessageProcessingStep Entity
AsynchronousOperations Entity
RemoteExecutionContext
IServiceEndpointNotificationService