xrmverse
Documentation menu

Webhooks

← xrmverse documentation

A webhook is a serviceendpoint row (contract 8, Webhook) and a processing step on it: when the step's message runs, xrmverse sends the execution context to your HTTP receiver. Webhooks need no Microsoft SDK on the receiver's side and no plug-in assembly, which makes them the way to extend xrmverse without a .NET Framework plug-in (see Plug-ins for those). Only contract 8 is supported; every other contract and authentication type is refused (400 0x80040203) at Create and Update.

Registering a webhook

An endpoint and a step, with any of the three clients.

CLI

xrmd webhook create --name new_hook --url https://hooks.example.test/in --auth header --value X-Api-Key=s3cr3t
xrmd plugin step add --webhook new_hook --message Create --entity account --stage 40 --mode async
xrmd webhook list
xrmd webhook show new_hook
xrmd webhook update new_hook --url https://hooks.example.test/v2
xrmd webhook delete new_hook --yes

--auth is key (one --value, sent as the query parameter code), header or query (--value K=V, repeatable). The CLI builds the settings XML. list and show print the url without its query string (a query may carry a key) and never the auth value: the server does not return it, isauthvalueset tells whether one is stored. plugin step add takes --webhook <name> instead of --type (exactly one of them, and --config is refused with --webhook); plugin list shows webhook steps under a "Webhooks" heading (in -o json they follow the plug-in rows, with a webhook property). Exit codes: 5 refused by the server, 4 not found, 2 usage.

Web API

POST /api/data/v9.2/serviceendpoints
{ "name": "new_hook", "contract": 8, "authtype": 5, "url": "https://hooks.example.test/in",
  "authvalue": "<settings><setting name=\"X-Api-Key\" value=\"s3cr3t\" /></settings>" }

POST /api/data/v9.2/sdkmessageprocessingsteps
{ "name": "new_hook: Create of account", "stage": 40, "mode": 1, "rank": 1,
  "eventhandler_serviceendpoint@odata.bind": "/serviceendpoints(<id>)",
  "sdkmessageid@odata.bind": "/sdkmessages(<id>)", "sdkmessagefilterid@odata.bind": "/sdkmessagefilters(<id>)" }

SDK (SOAP): create an Entity("serviceendpoint") with contract = OptionSetValue(8), authtype, url and authvalue, then a step whose eventhandler is EntityReference("serviceendpoint", id) and whose plugintypeid is empty. The Plug-in Registration Tool's "Register New WebHook" does the same.

Columns and rules

Column Rule
contract 8 (required)
authtype 4 Webhook Key, 5 Http Header, 6 Http Query String
url absolute http or https, no user info, no fragment; port 80 or 443 unless Pipeline:WebhookAllowAnyPort is true
authvalue required on Create, write-only (never returned). Type 4: any non-blank string. Types 5 and 6: <settings><setting name="K" value="V" />…</settings> with at least one setting and non-blank names
isauthvalueset read-only Boolean
messageformat, messagecharset, connectionmode, userclaim null, or: messageformat 2 (Json), messagecharset 0 (Default) or 1 (UTF8), connectionmode 1 (Normal), userclaim 1 (None); the others apply to Azure Service Bus and are refused
namespaceaddress, path, solutionnamespace, saskeyname must be null

A header name must be an HTTP token, not Content-Type, Content-Length, Host or Transfer-Encoding, and not start with x-ms-; header values must be printable ASCII. A step on a webhook has eventhandler = the endpoint and no plugintypeid and no configuration; stage, mode, filter, filtering attributes, images, rank, impersonating user and asyncautodelete follow the plug-in step rules, and so does a custom API's allowedcustomprocessingsteptype. Deleting a webhook deletes the endpoint, its steps and their images; its queued jobs are cancelled (3/32). No step runs on a message of serviceendpoint, not even one without a table filter (its messages are not events). A step's handler changes through an Update of eventhandler; Associate and Disassociate over plugintype_sdkmessageprocessingstep or serviceendpoint_sdkmessageprocessingstep are refused (400). The table is serviceendpoint (object type code 4618, see Tables in every organization).

The request sent

POST {url} with the RemoteExecutionContext as the JSON body (Content-Type: application/json; charset=utf-8), produced by the DataContractJsonSerializer over the SDK types, so the shape is the one the SDK defines. The SDK stays bring-your-own: webhooks need it installed on the server like plug-ins do (see Installing the Microsoft SDK).

Header Value
x-ms-dynamics-organization the organization name (Dataverse sends its domain name)
x-ms-dynamics-entity-name PrimaryEntityName
x-ms-dynamics-request-name MessageName
x-ms-correlation-request-id CorrelationId

Authentication: type 4 adds the query parameter code; type 5 adds the headers; type 6 adds the query parameters. The existing query parameters of the url are kept. A body over 256 KB (262,144 bytes) is sent again trimmed of ParentContext, InputParameters, PreEntityImages and PostEntityImages, with the header x-ms-dynamics-msg-size-exceeded: true. The sender honours the standard HTTP_PROXY, HTTPS_PROXY and NO_PROXY environment settings of .NET.

Outcomes, retries and where failures show

A 2xx answer is success. Anything else is a delivery failure: 500 0x80040224, message Endpoint unavailable: webhook '{name}' {reason}. where the reason is returned {status} {reason phrase}, did not answer within {n} s or could not be reached ({error}). The message never contains the query string, a header or the auth value.

  • Timeout per attempt: Pipeline:WebhookTimeout, a time span, default 00:01:00, must be positive and at most 24.20:31:23.647 (int.MaxValue milliseconds).
  • One immediate extra attempt only for 502, 503 and 504.
  • Retryable: any 5xx (after that extra attempt), a timeout and a connection or TLS error. Final: 3xx (no redirect is followed) and 4xx.
Step Failure Result
synchronous any the request fails with the fault and the transaction rolls back; the POST cannot be recalled, so the receiver may already have acted
asynchronous retryable the job is retried with the async queue's backoff (Pipeline:AsyncMaxRetries, default 3), then ends 3/31
asynchronous final the job ends 3/31 at once

For asynchronous steps the failure shows in the system job: asyncoperation.message holds the fault message and owningextensionid is the step:

GET /api/data/v9.2/asyncoperations?$filter=_owningextensionid_value eq <step id>&$select=statecode,statuscode,message

asyncautodelete on a step deletes the succeeded system job (plug-in and webhook steps). No plugintracelog rows are written for webhook steps. The async job runs as the owner of its row, as for plug-ins (see Plug-ins).

Configuration

Key Default Meaning
Pipeline:WebhookTimeout 00:01:00 time to wait for each attempt; a positive TimeSpan of at most 24.20:31:23.647
Pipeline:WebhookAllowAnyPort false allow urls on ports other than 80 and 443 (tests, development, on-premises receivers)

Known limits

  • Contracts other than 8 and IServiceEndpointNotificationService are not supported. Service Bus queue and topic and Event Hub contracts: #84.
  • Webhooks and their steps cannot be added to a solution (component 95), and a solution export never contains them, and a solution import that holds a ServiceEndpoints node or a step with EventHandlerTypeCode 4618 is refused: register them per organization. #85.
  • x-ms-dynamics-organization is the organization name, not Dataverse's domain name.
  • The authvalue is stored unencrypted in meta.serviceendpoint_secret of the organization database, outside the table so that no read path returns it: protect access to the database and its backups. #86.
  • The automatic AsynchronousStepPrimaryName image of an asynchronous step is not added. #87.
  • The sync failure code 0x80040224 is not verified against Dataverse (Learn names the error dialog, not the code), and neither is the retry of async jobs beyond the one extra attempt (see the checklist).
  • TriggerServiceEndpointCheck is not served.
  • Collection-valued navigation properties are not modelled: serviceendpoint_sdkmessageprocessingstep is not in $metadata, and $expand and the navigation path return 400. The single-valued eventhandler_serviceendpoint of a step works. #88.
  • The web admin Plug-ins page lists plug-in steps only; use xrmd webhook and xrmd plugin list.

The manual acceptance checklist is docs/superpowers/acceptance/webhook-checklist.md.