Use a synchronous C# plugin when a failure in the external system must prevent the Dataverse record from saving. Use an Azure webhook when it must not. Use a Custom API when an external caller needs a named operation with declared inputs and outputs instead of raw table access. Most integration problems in this ecosystem trace back to picking the wrong one of those three.
The comparison
| Synchronous plugin | Azure webhook | Custom API | |
|---|---|---|---|
| Runs inside the transaction | Yes | No | Yes, when registered synchronously |
| Can roll back the save | Yes | No | Yes |
| User waits for it | Yes — every time | No | Only if called synchronously |
| Callable from outside | No | n/a — it calls outward | Yes, as a named operation |
| Right for | Validation, enforced consistency | Notification, downstream propagation | External-facing operations |
The row that decides most architectures is the third. A synchronous plugin calling an external service puts that service's latency in front of every user who saves a record. If the endpoint takes two seconds, every save takes two seconds — and if it is down, nobody can save at all.
Building a Custom API
A Custom API declares its request and response parameters explicitly, which means the contract is discoverable instead of tribal. External developers read it from /api/data/v9.2/$metadata instead of asking someone which fields to send.
Authentication
Outbound and inbound integrations authenticate through Entra ID with the client credentials grant, against an application user holding a security role scoped to exactly the tables the integration touches.
Scope it properly at the start. An application user with broad privileges is the finding that turns a routine security review into a remediation project, and narrowing permissions after integrations depend on them is considerably harder than setting them correctly on day one.
Designing for failure
- Service protection limits. Dataverse returns HTTP 429 when a caller exceeds its allowance. Handle it with exponential backoff — through API Management, a Polly policy, or equivalent.
- Idempotency. Assume every message can arrive twice. Key on a correlation identifier so a redelivery updates instead of duplicating.
- Dead-letter handling. A message that cannot be processed has to land somewhere a person will look. Silent discards are how a month of missing records is discovered at close.
- Bounded synchronous work. If a plugin must call outward synchronously, give it a short timeout and decide explicitly what happens when it expires.
The integration that fails loudly on the first bad message costs an hour. The one that swallows failures costs a reconciliation nobody can complete.
In practice
Most estates end up using all three: plugins for the handful of rules that must be enforced at write time, webhooks for everything downstream, and Custom APIs as the surface external systems are allowed to touch. What keeps that coherent is deciding the question per integration — does this system get a vote on the save — instead of standardising on whichever pattern the last project used.