Architecture

Dataverse Integration Choices: Webhooks vs Service Bus vs Plug-ins vs Flows

Compare Dataverse plug-ins, webhooks, Azure Service Bus endpoints and Power Automate triggers for sending events to external systems, with limits, failure behaviour and setup steps.

12 min read
On this page

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-inWebhookAzure Service Bus endpointPower Automate trigger
What runsYour .NET code in the Dataverse sandboxHTTP POST to your URLMessage posted to a queue, topic, relay or Event HubsA cloud flow
Synchronous optionYesYesNo, asynchronous onlyNo
Inside the database transactionYes, for synchronous stepsSynchronous steps onlyNoNo
BufferingNoneNone; scales as far as your endpoint doesFull queuing with queue and topic contractsNot applicable
Retry on failureUp to your codeOne extra attempt on 502, 503, 504Asynchronous service retries with growing intervalsDepends on flow design
PayloadIPluginExecutionContextRemoteExecutionContext as JSONRemoteExecutionContext as .NET binary, JSON or XMLTrigger outputs
Payload trimmingNot applicableAbove 256 KBAbove 192 KBNot applicable
SkillsC# developerAny web stackAzure messagingMaker

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:

StageTransaction
PreValidationFor the initial operation, runs before the database transaction and before security checks
PreOperationInside the transaction; the place to change values in the message
MainOperationInternal use, except custom APIs and virtual table providers
PostOperationInside 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:

AuthenticationHow it's sent
HttpHeaderOne or more key-value pairs in request headers
WebhookKeyQuery string ?code=<value>, which suits Azure Functions keys
HttpQueryStringOne 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 example Update.
  • 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 case ParentContext, InputParameters, PreEntityImages and PostEntityImages are 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:

ContractBehaviour
QueuePersistent queue; no active listener needed
TopicLike a queue, but multiple subscribers can receive each message
One-wayRelay; requires an active listener or the post fails and is retried
Two-wayRelay; the listener can return a string to the calling plug-in
RESTTwo-way on a REST endpoint
Event HubsPosts 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

ScenarioRecommended mechanismWhy
Block a save if an external check failsSynchronous plug-in, or synchronous webhookRuns inside the transaction
Push every change of a busy table to an ERP or data platformService Bus queue or topic endpointDurable buffering and asynchronous retries
Several independent consumers for the same eventsService Bus topicEach subscription gets its own copy
Notify a single API you own, moderate volumeAsynchronous webhookSimple JSON over HTTPS, no Azure messaging needed
Business process owned by makers, low volumePower Automate triggerNo code; uses connectors
Enrich or reshape data before it's savedPreOperation plug-inOnly stage designed for changing values in the message
Call an external API that can take more than a few secondsAsynchronous plug-in, webhook or Service BusKeeps 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 Send permission.

Step 1: Register a webhook

  1. Run the Plug-in Registration tool, select Create New Connection, sign in and choose the environment.
  2. Select the Register New WebHook option.
  3. 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.
  4. Select the new webhook and choose Register New Step.
  5. 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.
  6. 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

  1. In the Service Bus namespace, create the queue or topic and a SAS policy on it with at least the Send permission (a two-way relay also needs Listen). Copy the policy's connection string.
  2. 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.
  3. Select the endpoint, choose Register > Register New Step, and fill in the message and table, for example Create on account. Service Bus steps run asynchronously.
  4. 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,url

Then 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 asyncoperations filtered on statuscode eq 31 and 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

Questions people ask

Can a Dataverse webhook run synchronously?

Yes. Webhook steps can be registered as synchronous or asynchronous. A synchronous webhook failure is shown to the user as an Endpoint unavailable error and the data operation rolls back, but a request that was already sent to the endpoint can't be recalled.

What is the timeout for a Dataverse webhook?

Dataverse waits 60 seconds for a response. A non-2xx status or no response within the timeout fails the operation, except for 502, 503 and 504 responses, which get exactly one more attempt.

Does the Dataverse Service Bus integration support synchronous steps?

No. Microsoft's comparison states that Azure Service Bus integration only allows asynchronous steps. Posts are made by system jobs of the asynchronous service, which retries with increasing intervals when the bus or listener is unavailable.

Why does my Power Automate Dataverse trigger run more often than expected?

The When a row is added, modified or deleted trigger evaluates every update, even when values don't change, and the Select columns filter fires whenever a listed column is included in the update request. Remove columns that are always sent, such as the primary key, and add a Filter rows expression.

DataverseAzure Service BusWebhooksPower AutomatePlug-ins
  1. Power Automate error handling: try-catch scopes, run after and alerts

    Make Power Automate cloud flows fail gracefully: retry policies, Try, Catch and Finally scopes, result() to find the failed action, Teams alerts, error logging and a correct final run status.

    Architecture12 min read
  2. Power Platform ALM: ship apps and flows with managed solutions and pipelines

    Move Power Apps and Power Automate flows from development to test to production with managed solutions, environment variables, connection references and Power Platform pipelines.

    Architecture13 min read
  3. Webhooks that don't double-charge: retries, idempotency and dead letters

    Build webhook receivers that survive retries and duplicate deliveries: verify, deduplicate on the event ID, queue to Azure Service Bus, process idempotently and handle dead letters.

    Architecture12 min read