Calling an External API From a Dynamics 365 Plug-in When the Sandbox Blocks You

What sandbox isolation allows, when a synchronous call is acceptable, webhooks, service endpoints and the outbox pattern, and how to recover orders lost when a call failed silently.

Dataverse online runs plug-ins in a sandbox: outbound calls are allowed only over HTTP or HTTPS to a named host, not an IP address or localhost, with no file system access and a two-minute execution limit. That makes a direct call acceptable only when the save must wait for a short answer, such as a validation, with a tight timeout. Anything that sends data out belongs outside the transaction: a webhook, a Service Bus service endpoint or, best, an outbox row written in the same transaction and sent by an external worker. If orders were already lost when a call failed silently, define the failure window, compare both systems by business key and replay the missing orders idempotently.

What does the plug-in sandbox actually allow when calling an external API?

In Dynamics 365 and Dataverse online, every custom plug-in runs in sandbox isolation. It is not a setting you can switch off for an online environment, so the design has to fit inside it. The rules below are the ones that trip developers up most often; Microsoft revises the details, so confirm them in the current Dataverse documentation on plug-in isolation and accessing external web resources before relying on any single figure.

RuleWhat it means in practiceWhat developers try that fails
Outbound network over HTTP and HTTPS onlyHttpClient or WebClient calls to web APIs work; other protocols and raw sockets do not.Opening a TCP connection, SMTP, FTP or a database connection to an external server.
Named hosts only, no IP addresses or localhostThe endpoint must be reachable by a DNS name over the public internet.Calling an IP address, a loopback address, or an on-premises host that is not published through a gateway or reverse proxy.
No file system, registry or event log accessConfiguration and secrets come from the step configuration, environment variables or a secret store, and diagnostics go to the tracing service.Reading a config file, writing a local log, or loading certificates from the machine store.
A two-minute execution limitA plug-in that runs longer is terminated, whether synchronous or asynchronous.Retry loops with long waits, large paginated downloads, or calling a slow API several times in one execution.
Synchronous steps run inside the database transactionWhile the external call waits, the user waits and locks are held; if the save rolls back, the external call has still happened.Creating an order in an external system from a pre-operation or post-operation synchronous step.
Dependencies must be packagedThird-party libraries ship with the plug-in, for example as a plug-in package, and must work under the sandbox restrictions.Referencing an SDK that writes to disk or needs full trust, then finding it fails only in the cloud.
  • Set an explicit, short timeout on the HTTP client rather than relying on the default, so a slow API fails fast inside the execution limit.
  • Avoid keep-alive connections to external hosts from plug-ins, which Microsoft lists as a plug-in best practice, and create the HTTP client in a way that does not exhaust connections under load.
  • Never hard-code credentials. Use environment variables, including secret types backed by Azure Key Vault, secure step configuration, or managed identity for plug-ins where it is available to your tenant.
  • Log the call outcome, with a correlation identifier, through the tracing service; how to make those traces useful in production is covered in our guide to plug-in logging and diagnostics.

When is a short synchronous call from a plug-in the right choice?

Only when the save genuinely depends on the answer and nothing is written to the other system. Typical examples are validating an address or a tax number before a record is saved, or reading a reference value the user must see before the transaction completes. In those cases the call is a read, a failure can safely block or warn the user, and nothing has to be undone if the save later rolls back.

Keep it short and predictable: one call, a timeout of a few seconds, no retries inside the plug-in, and a clear error message or a deliberate fallback when the API is down. If the business cannot accept saves failing whenever the API is unavailable, the call does not belong in the transaction at all.

The call that must never be synchronous is the one that writes: creating an order, a shipment or an invoice in another system from inside the save. If the external call succeeds and the Dataverse transaction then rolls back because a later step fails, the other system has an order Dynamics 365 does not. If the call times out after the other system accepted it, you do not know whether to resend. Both are how orders go missing or appear twice.

Asynchronous step, webhook, service endpoint or outbox: which pattern fits?

Once the call leaves the transaction, there are four supported ways to get the data out. They differ mainly in where the code runs, what happens when the external API is down, and whether a failure is visible to anyone.

PatternWhere the call runsWhen the API is downFits when
Asynchronous plug-in stepIn the sandbox, after the transaction commits.The system job fails and shows in System Jobs, but there is no business-level retry unless you build one.Low volume, a tolerant API, and someone actually watches failed system jobs.
WebhookDataverse posts the execution context to an HTTPS endpoint you host, such as an Azure Function, which calls the API.Your endpoint decides; an asynchronous webhook that cannot be reached fails as a system job.You want real code and libraries outside the sandbox, with your own retries and logging.
Service endpoint to Azure Service BusDataverse posts the context to a queue or topic; a consumer you run calls the API.Messages wait in the queue and the consumer retries; failed posts from Dataverse show in System Jobs.Volume, several consumers, or bursts the external API cannot absorb.
Outbox with an external workerA plug-in writes an outbox row in the same transaction; a worker such as an Azure Function sends it.Rows stay pending and are retried with backoff; nothing is lost and nothing is sent for a rolled-back save.The default for anything that creates or changes data in another system.
  • The outbox is the default because it is the only pattern where "the record was saved" and "a message must be sent" are committed together. If the save rolls back, the outbox row rolls back with it.
  • The worker sends through one adapter per external API, upserts on a business key or sends an idempotency key, and marks the row sent only after the API confirms.
  • The outbox table design, retry with backoff, circuit breaking, Service Bus peek-lock and idempotent consumers are set out in our guide to rationalising point-to-point integrations and are not repeated here.
  • If the integration actually needs external systems to read Dynamics 365 data on demand, rather than Dynamics pushing to them, that is a different design, covered in our guide to exposing Dynamics 365 data through an API gateway.

Orders were lost when the plug-in call failed silently: how do you recover them?

Silent loss usually has one of three shapes. The plug-in caught the exception and carried on, so the save succeeded and the order was never sent. An asynchronous step or webhook failed and sat in System Jobs where nobody looked. Or the call succeeded and the save then rolled back, leaving an order in the other system that Dynamics 365 does not have. Recover in this order, and do not resend anything until step four.

  • Step 1, make new orders recoverable now: add a status column such as "sent to external system", set only after a confirmed response, or start writing outbox rows, so the loss stops growing while you recover the backlog.
  • Step 2, define the failure window: find the first failure from plug-in trace logs, failed System Jobs, Application Insights or the external API logs, and take the window from a little before it to now.
  • Step 3, compare both systems by business key: export orders created or changed in the window from Dataverse and from the external system, and list the orders missing on each side. Missing externally means lost; present externally but not in Dataverse means a rolled-back save.
  • Step 4, check idempotency before resending: confirm the external API upserts on the order number or accepts an idempotency key, or check existence per order first, so a replay cannot create duplicates.
  • Step 5, replay in a controlled batch: send a small batch through the new outbox or worker, check it in the external system, then send the rest, logging each order and response.
  • Step 6, reconcile and close: rerun the comparison until both sides match, resolve orphaned external orders with the business, tell anyone affected, and move the integration permanently to the outbox pattern with alerts on failures and on silence.

How do you stop a plug-in integration failing silently again?

Make every failure visible to a person who owns it, and make every success provable. That is design rather than tooling, and most of it can be added to an existing integration without rewriting it.

  • Never swallow an exception from an external call inside a plug-in. Either fail the operation deliberately or hand the work to an outbox that records the failure.
  • Alert on failed system jobs for the integration steps, and on silence: no orders sent in a period when orders were created.
  • Reconcile daily by business key between Dynamics 365 and the external system, with an owner for every difference.
  • Keep secrets and endpoints in environment variables with expiry tracking, so a rotated key does not quietly stop the integration.
  • Give each integration a business owner who knows what a day of failure costs, and a technical owner who can fix it.

Who should build and rescue a plug-in integration like this?

We recommend the right solution - whether that's Microsoft Dynamics 365, Power Platform, or a custom-built CRM. Some businesses need the Microsoft ecosystem. Others need full control without licensing. We deliver both.

Production integration rescue is senior development work: reading trace logs and system jobs under pressure, reconciling two systems by key, replaying safely and then moving the integration onto a pattern that cannot lose data. Our senior consultants and full-stack developers, with 8+ years of Dynamics 365 Customer Engagement and Power Platform delivery, write C# plug-ins, custom APIs, Azure Functions and Service Bus consumers, and can join your team as described on our Dynamics 365 developer page. We work on the Dynamics 365 side and the integration layer; we integrate with ERP and order systems rather than implementing them.

What do people ask us?

What is the right way to call an external API from a Dynamics 365 plug-in?

Call it directly only for a short read the save depends on, such as a validation, with a tight timeout and no retries in the plug-in. For anything that creates or changes data in another system, write an outbox row in the same transaction and let an external worker send it with retries and idempotent writes, or post to a webhook or Azure Service Bus service endpoint so the call runs outside the transaction.

What does sandbox isolation block in a Dataverse plug-in?

Online plug-ins run in sandbox isolation. Outbound calls are limited to HTTP and HTTPS to named hosts, not IP addresses or localhost; there is no access to the file system, registry or event log; and execution is limited to two minutes. Dependencies must be packaged with the plug-in and work under those restrictions. Microsoft revises the details, so check the current documentation.

Why should a plug-in not create an order in another system synchronously?

Because the external call cannot be rolled back. If it succeeds and the Dataverse transaction then fails, the other system has an order Dynamics 365 does not. If it times out after the other system accepted it, you cannot tell whether to resend. An outbox row committed with the save, sent afterwards by a worker that upserts on a business key, avoids both.

Should I use a webhook or an Azure Service Bus service endpoint from Dynamics 365?

A webhook posts the execution context to an HTTPS endpoint you host, which suits modest volume and code that needs libraries the sandbox will not run. A Service Bus service endpoint posts to a queue or topic, which suits higher volume, several consumers or an external API that cannot absorb bursts, because messages wait and the consumer retries. Either way, watch failed system jobs.

How do we recover orders lost when a plug-in API call failed silently?

First make new orders recoverable with a sent status or outbox rows. Then define the failure window from trace logs and system jobs, compare Dataverse and the external system by order number to find what is missing on each side, confirm the external API is idempotent, replay a small batch, verify, send the rest, and reconcile until both systems match.

Can a Dynamics 365 plug-in call an on-premises system?

Not directly by IP address or internal host name, because the sandbox only reaches named hosts over HTTP or HTTPS. Publish the on-premises API through a secure gateway or reverse proxy with a public DNS name and proper authentication, or better, send the work through Azure Service Bus or an outbox and let a worker inside your network collect it.

Which solution is right for your business?

Tell us what you need. A senior consultant replies within one business day with a recommendation - Dynamics 365, Power Platform, or a custom-built CRM - not a sales script.