To send Dataverse events to an external system, use an asynchronous webhook when a web endpoint you control can absorb the traffic, an Azure Service Bus queue or topic endpoint when you need durable queuing at high volume or several consumers, a plug-in when logic must run inside the transaction or change the data, and a Power Automate flow when a maker-owned, low-code process is enough. Webhooks and Service Bus both deliver the same RemoteExecutionContext payload and are registered with the Plug-in Registration tool; the differences are synchronous support, buffering, retry behaviour and who operates the receiving side.
Who this is for and what you will have at the end
This guide is for architects and developers who need Dataverse (or a Dynamics 365 app built on it) to notify another system, such as an ERP, a data platform or a microservice, when rows are created, updated or deleted.
At the end you will have:
- A comparison of the four mechanisms by execution mode, limits and failure behaviour.
- A decision guide for common integration scenarios.
- The steps to register a webhook and a Service Bus endpoint, and to verify and troubleshoot them.
The options at a glance
| Plug-in | Webhook | Azure Service Bus endpoint | Power Automate trigger | |
|---|---|---|---|---|
| What runs | Your .NET code in the Dataverse sandbox | HTTP POST to your URL | Message posted to a queue, topic, relay or Event Hubs | A cloud flow |
| Synchronous option | Yes | Yes | No, asynchronous only | No |
| Inside the database transaction | Yes, for synchronous steps | Synchronous steps only | No | No |
| Buffering | None | None; scales as far as your endpoint does | Full queuing with queue and topic contracts | Not applicable |
| Retry on failure | Up to your code | One extra attempt on 502, 503, 504 | Asynchronous service retries with growing intervals | Depends on flow design |
| Payload | IPluginExecutionContext | RemoteExecutionContext as JSON | RemoteExecutionContext as .NET binary, JSON or XML | Trigger outputs |
| Payload trimming | Not applicable | Above 256 KB | Above 192 KB | Not applicable |
| Skills | C# developer | Any web stack | Azure messaging | Maker |
Microsoft's own comparison of webhooks and Service Bus says Service Bus works for high-scale processing and provides a full queuing mechanism when Dataverse pushes many events, while webhooks can only scale as far as your hosted service can handle the messages. Both can also be invoked from a plug-in or a custom workflow activity.
How each mechanism behaves
Plug-ins
A plug-in is a .NET class that implements IPlugin and is registered as a step on a message (Create, Update, Delete and so on), a table and a pipeline stage:
| Stage | Transaction |
|---|---|
| PreValidation | For the initial operation, runs before the database transaction and before security checks |
| PreOperation | Inside the transaction; the place to change values in the message |
| MainOperation | Internal use, except custom APIs and virtual table providers |
| PostOperation | Inside the transaction; asynchronous steps run afterwards through the asynchronous service |
Plug-ins are the most performant way to apply custom logic and can call external systems, but they have a hard time limit. A Dataverse message operation, including all its synchronous and asynchronous plug-ins, must complete within 2 minutes or Dataverse throws a TimeoutException and rolls back the operation. Microsoft recommends keeping a plug-in under 2 seconds and registering anything slower as asynchronous.
Use a plug-in when you must validate or change data before it's saved, or when the external call is quick and its failure should block the save. Avoid long outbound calls in synchronous plug-ins: they hold the user's transaction open.
Webhooks
A webhook is a service endpoint that posts the execution context to an HTTPS URL. You register it in the Plug-in Registration tool with the Register New WebHook option, giving a name, endpoint URL and one of three authentication options:
| Authentication | How it's sent |
|---|---|
HttpHeader | One or more key-value pairs in request headers |
WebhookKey | Query string ?code=<value>, which suits Azure Functions keys |
HttpQueryString | One or more key-value pairs in the query string |
Registered webhooks support only port 80 for HTTP and port 443 for HTTPS. Each request carries the RemoteExecutionContext as JSON in the body and these headers:
x-ms-dynamics-organization: the environment's domain name.x-ms-dynamics-entity-name: the table's logical name.x-ms-dynamics-request-name: the message, for exampleUpdate.x-ms-correlation-request-id: used by the platform for loop prevention and by support for telemetry.x-ms-dynamics-msg-size-exceeded: present only when the payload exceeds 256 KB. In that caseParentContext,InputParameters,PreEntityImagesandPostEntityImagesare removed.
Delivery rules are simple. Dataverse waits 60 seconds. Anything other than a 2xx response fails the operation, except 502, 503 and 504, which get exactly one more attempt. Dataverse ignores the response body.
- Asynchronous steps record the outcome in a System Job, so failures are visible and queryable.
- Synchronous steps show the user an Endpoint unavailable dialog on failure. The data operation rolls back, but the request already sent to your endpoint can't be recalled, so your endpoint may have processed an event for a save that never happened.
Azure Service Bus endpoints
With the Service Bus integration, Dataverse posts the execution context through the asynchronous service to a Service Bus namespace authorised with a Shared Access Signature. Each endpoint uses a contract:
| Contract | Behaviour |
|---|---|
| Queue | Persistent queue; no active listener needed |
| Topic | Like a queue, but multiple subscribers can receive each message |
| One-way | Relay; requires an active listener or the post fails and is retried |
| Two-way | Relay; the listener can return a string to the calling plug-in |
| REST | Two-way on a REST endpoint |
| Event Hubs | Posts to an Azure Event Hubs solution |
The message body can be .NET binary (the default), JSON or XML, so non-.NET listeners can read it. Each post is a system job; if the bus, endpoint or listener is unavailable, the asynchronous service keeps retrying at increasing intervals and the job sits in a Wait state. When the payload exceeds 192 KB, the same four context properties are removed; if it still exceeds 192 KB, the message isn't sent and an error occurs.
Because posts go through the asynchronous service, they aren't immediate. Microsoft's walkthrough mentions waiting about 10 minutes for a test post to appear.
Listeners on the Azure side should use a current Azure SDK library. The older .NET Service Bus libraries and the SBMP protocol reached end of support on 30 September 2026; see Azure Service Bus SBMP retirement: migrating to Azure.Messaging.ServiceBus.
Power Automate
The Dataverse connector's When a row is added, modified or deleted trigger runs a cloud flow for a table and scope (User, Business Unit, Parent: Child business unit or Organization). The maker who creates the flow needs user-level create, read, write and delete permissions on the Callback Registration table. Useful options:
- Select columns: for updates, run only when listed columns are included in the request. Lookup columns aren't supported in this filter.
- Filter rows: an OData-style expression such as
firstname eq 'John', evaluated after the change is saved. - Delay until: hold the run until a UTC time; unlike the Delay until action, it never expires.
- Run as: flow owner, row owner or modifying user for later Dataverse actions; requires the Act on Behalf of Another User privilege.
The trigger doesn't support 1:N or N:N relationship changes, and it evaluates every update, even if values didn't change. Every action in the flow consumes Power Platform requests from the owner's or flow's entitlement, which matters for high-volume tables.
Decision guide
| Scenario | Recommended mechanism | Why |
|---|---|---|
| Block a save if an external check fails | Synchronous plug-in, or synchronous webhook | Runs inside the transaction |
| Push every change of a busy table to an ERP or data platform | Service Bus queue or topic endpoint | Durable buffering and asynchronous retries |
| Several independent consumers for the same events | Service Bus topic | Each subscription gets its own copy |
| Notify a single API you own, moderate volume | Asynchronous webhook | Simple JSON over HTTPS, no Azure messaging needed |
| Business process owned by makers, low volume | Power Automate trigger | No code; uses connectors |
| Enrich or reshape data before it's saved | PreOperation plug-in | Only stage designed for changing values in the message |
| Call an external API that can take more than a few seconds | Asynchronous plug-in, webhook or Service Bus | Keeps the user's transaction short |
Two architecture notes apply whichever you choose. First, design receivers to be idempotent: webhooks can retry on gateway errors, asynchronous jobs can be retried, and synchronous webhooks can deliver events for saves that roll back. Second, if the target system also writes back to Dataverse, use an application user and follow Dataverse service protection API limits on the write path. For a broader comparison of brokers, see Kafka vs RabbitMQ vs AWS SQS for event-driven architecture.
Prerequisites
- The Plug-in Registration tool, downloaded as described in Microsoft's Dataverse development tools documentation.
- A Dataverse account with System Customizer or System Administrator in the environment.
- For webhooks: an HTTPS endpoint, for example an Azure Function, and the key or header values it expects.
- For Service Bus: a namespace with a queue or topic and a SAS policy with at least the
Sendpermission.
Step 1: Register a webhook
- Run the Plug-in Registration tool, select Create New Connection, sign in and choose the environment.
- Select the Register New WebHook option.
- Enter a Name, the Endpoint URL (for an Azure Function, the function URL without the key) and the authentication type. For an Azure Function key, choose WebhookKey and enter only the key value.
- Select the new webhook and choose Register New Step.
- Choose the Message (for example
Update), the Primary Entity, and Filtering Attributes so the step only fires for the columns you care about. The tool prompts you if you leave them empty. - Choose PostOperation and Asynchronous unless you have a reason to block the save, and decide whether to delete the System Job on success.
Your endpoint should validate the key, read x-ms-dynamics-request-name and x-ms-dynamics-entity-name, parse the JSON body, queue the work and return a 2xx quickly.
Step 2: Register a Service Bus endpoint
- In the Service Bus namespace, create the queue or topic and a SAS policy on it with at least the
Sendpermission (a two-way relay also needsListen). Copy the policy's connection string. - In the Plug-in Registration tool, select Register > Register New Service Endpoint, choose the option to start with the connection string from the Azure Service Bus portal, paste it and select Next. In the Service Endpoint Registration form, set the Designation Type (queue or topic) and Message Format (JSON is the most portable), then select Save.
- Select the endpoint, choose Register > Register New Step, and fill in the message and table, for example
Createonaccount. Service Bus steps run asynchronously. - Deploy a listener that receives from the queue or subscription.
Verification
Query registered webhooks (they have contract value 8):
GET [organization URI]/api/data/v9.0/serviceendpoints?$filter=contract eq 8&$select=serviceendpointid,name,authtype,urlThen trigger the event, for example by updating a row, and check delivery:
- Webhook, asynchronous: in the app, open Settings > System > System Jobs and look for entries with a Status Reason of Failed, or query
asyncoperationsfiltered onstatuscode eq 31and the step's_owningextensionid_value. - Service Bus: open the System Job named after your endpoint and check whether it succeeded, is waiting, or failed; then confirm the message count in the queue or subscription.
- Power Automate: open the flow's run history and confirm one run per qualifying change.
Authentication values for a webhook are stored in the AuthValue column and can't be read back, so keep the key in your own secret store.
Troubleshooting
Users see "Endpoint unavailable" when saving. A synchronous webhook failed or timed out. Download the log from the dialog, check the endpoint's availability and authentication, and consider switching the step to asynchronous.
Asynchronous webhook System Jobs fail. Open the failed job for details. Common causes are a wrong key, an endpoint returning a non-2xx status, or processing taking longer than 60 seconds; return quickly and process in the background.
Webhook payload is missing InputParameters or images. The request exceeded 256 KB and x-ms-dynamics-msg-size-exceeded was set. Retrieve the row by its PrimaryEntityId instead of relying on images, or reduce the columns in images.
Service Bus jobs stay in Wait. The namespace, entity or listener isn't reachable. For a one-way, two-way or REST relay contract, an active listener is required; queue and topic contracts don't need one.
Service Bus message not sent at all. The context exceeded 192 KB even after the parent context, input parameters and images were removed. Reduce the payload, for example by trimming entity images.
A flow runs on every save. Select columns includes a column that's always in the update request, such as the primary key. Remove it and add a Filter rows expression.
TimeoutException and rolled-back saves. A synchronous plug-in or the whole operation exceeded 2 minutes. Move the external call to an asynchronous plug-in, webhook or Service Bus step.
Checklist
- Each integration's mechanism chosen from the decision guide, with a written reason.
- Asynchronous registration by default; synchronous only where the save must be blocked.
- Filtering attributes set on every Update step.
- Receivers idempotent and quick to acknowledge.
- Webhook keys and SAS keys stored in a secret store and rotated.
- Payload size checked against the 256 KB (webhook) and 192 KB (Service Bus) thresholds.
- System Jobs monitored for failures; Service Bus dead-letter queues monitored on the Azure side.
- Power Automate triggers filtered by columns and rows, with request consumption reviewed.
References
- Use webhooks to create external handlers for server events
- Register a webhook
- Azure Service Bus integration for Dataverse
- Walkthrough: Configure Microsoft Azure (SAS) for integration with Dataverse
- Walkthrough: Register an Azure-aware plug-in using the Plug-in Registration tool
- Event framework in Microsoft Dataverse
- Use plug-ins to extend business processes
- Analyze plug-in performance
- Trigger flows when a row is added, modified, or deleted
- Requests limits and allocations