Agent skill
Microsoft Dynamics 365 (Dataverse) Webhooks Skill
Receive and authenticate Microsoft Dynamics 365 (Dataverse) webhooks registered with the Plug-in Registration Tool. Use when building a Dynamics 365 or Dataverse webhook receiver, securing it with the HttpHeader authentication option (Dataverse has no HMAC signature), parsing the RemoteExecutionContext JSON body (MessageName, PrimaryEntityName, InputParameters), handling Create, Update and Delete events, or debugging Dataverse webhook failures, timeouts and retries.
Install this skill
npx skills add hookdeck/webhook-skills --skill microsoft-dynamics-webhooks
Dynamics 365 webhooks are a Dataverse service endpoint (contract 8) plus one or more steps you register with the Plug-in Registration Tool. When a step's message fires (for example Create on contact), Dataverse POSTs the event's execution context to your URL as JSON.
This differs from most providers in three ways:
- There is no signature. Dataverse doesn't sign the body. There's no HMAC, signing secret or timestamp header. Instead you register the webhook with an authentication option that attaches a credential to every request.
- The event is in the body. The body is a serialized
RemoteExecutionContext. The event name isMessageName(Create,Update,Delete) and the table isPrimaryEntityName. - Only the status code matters. Dataverse ignores your response body. A non-2xx on a synchronous step fails the user's operation.
When to Use This Skill
- How do I receive Dynamics 365 / Dataverse webhooks?
- How do I verify Dynamics 365 webhook signatures? (You can't. Use the
HttpHeaderauthentication option and compare the secret.) - How do I register a webhook in the Plug-in Registration Tool?
- How do I handle
Create,UpdateandDeleteevents for a table such ascontactoraccount? - What is in the
RemoteExecutionContextbody, and how do I readInputParameters? - Why did my Dataverse webhook fail with "Endpoint unavailable"?
Verification (core): the HttpHeader authentication option
When you register the webhook, choose Authentication: HttpHeader and add a key/value pair, for example:
X-Webhook-Secret: 3f9c0e...a long random hex value...
Dataverse sends it on every request. Check it in constant time before you parse the body:
const crypto = require('crypto');
// NOT a Dataverse signature. This is a secret YOU register in the HttpHeader option.
function verifyDynamicsSecret(headerValue) {
const expected = process.env.DYNAMICS_WEBHOOK_SECRET;
if (!expected) throw new Error('DYNAMICS_WEBHOOK_SECRET is not set'); // fail closed
if (typeof headerValue !== 'string') return false;
const a = Buffer.from(headerValue);
const b = Buffer.from(expected);
// Length check first: timingSafeEqual throws on a length mismatch.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express lowercases header names: req.get('X-Webhook-Secret') works too.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Return 401 for a missing or wrong secret and 500 when the secret isn't configured, so the endpoint never accepts everything by accident.
Don't use WebhookKey or HttpQueryString. They put the credential in the query string (?code=... or ?Key1=Value1), where it ends up in access logs and proxies. Hookdeck's source verification doesn't cover them either. See references/verification.md.
The Payload: RemoteExecutionContext
{
"MessageName": "Update",
"PrimaryEntityName": "contact",
"PrimaryEntityId": "6d81597f-0f9f-e711-8122-000d3aa2331c",
"OperationId": "4af10637-4ea2-e711-8122-000d3aa2331c",
"OrganizationName": "OrgName",
"Stage": 40,
"Mode": 1,
"InputParameters": [
{
"key": "Target",
"value": {
"__type": "Entity:http:\/\/schemas.microsoft.com\/xrm\/2011\/Contracts",
"Id": "6d81597f-0f9f-e711-8122-000d3aa2331c",
"LogicalName": "contact",
"Attributes": [
{ "key": "firstname", "value": "James" },
{ "key": "fullname", "value": "James Glynn (sample)" }
]
}
}
],
"OwningExtension": { "Id": "75417616-4ea2-e711-8122-000d3aa2331c", "LogicalName": "sdkmessageprocessingstep" }
}
InputParametersandAttributesare arrays of{key, value}pairs, not objects. Flatten them before use. Dates look like"\/Date(1506384247000)\/".Updatecarries only the attributes in the request (and only those matching your step's Filtering Attributes). Register images or re-fetch the row for the full record.- Over 256 KB, Dataverse adds
x-ms-dynamics-msg-size-exceededand removesParentContext,InputParameters,PreEntityImagesandPostEntityImages. Re-fetch the row byPrimaryEntityIdthrough the Web API.
Dataverse also sends x-ms-dynamics-organization, x-ms-dynamics-entity-name, x-ms-dynamics-request-name and x-ms-correlation-request-id headers.
Events (MessageName values)
MessageName | Fires when |
|---|---|
Create | A row is created in the step's table |
Update | A row is updated (optionally only for the step's filtering attributes) |
Delete | A row is deleted |
You register one step per message and table, so you choose which events you receive. If a MessageName isn't one you handle, log it and return 200. If the body has no MessageName, return 400.
Delivery, Retries and Idempotency
- Success is any 2xx. The timeout is 60 seconds.
- Dataverse retries once, and only on
502,503or504. Any other failure isn't retried. - Webhooks only support ports 80 (HTTP) and 443 (HTTPS).
- Synchronous steps fail the user's save when your endpoint errors (an "Endpoint unavailable" dialog). The webhook call itself can't be recalled. Asynchronous steps record the failure in a System Job.
- There's no delivery ID header. Dedupe on
OperationIdplus the step id (OwningExtension.Id).
Environment Variables
# REQUIRED. The value you registered under the HttpHeader option. Generate with: openssl rand -hex 32
DYNAMICS_WEBHOOK_SECRET=
# OPTIONAL. The header key you registered (case-insensitive). Default: X-Webhook-Secret
DYNAMICS_WEBHOOK_HEADER=X-Webhook-Secret
Local Development
npx hookdeck-cli listen 3000 microsoft-dynamics --path /webhooks/microsoft-dynamics
No account required. The CLI creates a guest account on first run and gives you a public HTTPS URL to use as the webhook's Endpoint URL, plus a web UI for inspecting requests.
In production, Hookdeck's Microsoft Dynamics source type checks the header for you. See references/setup.md.
Reference Materials
- references/overview.md - How Dataverse webhooks work, RemoteExecutionContext, events, headers, limits
- references/setup.md - Registering the webhook and steps in the Plug-in Registration Tool
- references/verification.md - Why there is no signature, the HttpHeader pattern, gotchas