Building Custom REST API Connectors in Business Central: Exposing Data Securely for Third-Party Integrations and Mobile Apps

Building Custom REST API Connectors in Business Central: Exposing Data Securely for Third-Party Integrations and Mobile Apps

Business Central’s strength lies in its ability to integrate with modern cloud applications, but the real challenge emerges when you need to expose your financial and operational data through APIs that external systems and mobile clients can reliably consume. Out-of-the-box endpoints expose standard entities, but most integration scenarios demand custom data shapes, calculated fields, filtered subsets, or aggregate views tailored to specific business processes. Building these custom REST API connectors requires careful architectural decisions around authentication, data filtering, performance optimization, and monitoring, particularly in organizations handling high transaction volumes or supporting third-party mobile applications.

Understanding Business Central’s Native API Framework

Business Central exposes data through two primary API mechanisms: OData v4 endpoints for standard pages and tables, and the newer Pages API designed for application-level consumption. The Pages API builds on the modern AL language and provides a more controlled, versioned approach to data exposure than direct table access. Unlike Dynamics 365, which includes a unified Microsoft Dataverse layer, Business Central requires you to explicitly architect data exposure through custom API pages, which means your integration strategy must balance between leveraging the platform’s built-in capabilities and accepting responsibility for designing clean, maintainable contracts between Business Central and consuming systems.

The distinction matters operationally. Standard OData endpoints expose the database schema directly, which couples external consumers tightly to your internal data model. When you restructure a table or rename a field for internal reasons, external integrations break. Custom API pages decouple that contract, allowing you to evolve your internal schema independently from the promises you have made to external systems.

Designing Custom API Pages for External Consumption

Custom API pages in Business Central are AL procedures exposed through the REST endpoint /api/v2.0/companies({company-id})/custom-apis/. Define these using the Page object subtype, marking them as API and APIVersion to control versioning and backward compatibility. The page structure mirrors a standard page but operates as a service layer, processing inbound requests and returning structured JSON responses rather than rendering a user interface.

The design pattern establishes a clear separation: your custom page receives the incoming request, validates it, queries the underlying tables or reports with any necessary business logic, transforms the result into the contracted JSON shape, and returns it. This allows you to filter at the data layer, aggregate values, apply security row-level filters, and enforce business logic before data leaves the system. For example, a third-party field service application might request customer shipment history and location data. Rather than exposing raw table access, you create a custom API page that joins customer, sales order, and shipment tables, filters by the authenticated user’s assigned territory, calculates on-time delivery percentage, and returns only the fields the mobile app requires.

Authentication and authorization flow through the AL code itself. Within the API page, AL procedures can access the authenticated user’s context and apply permissions hierarchically. A request arriving with a specific OAuth token corresponds to a Business Central user; that user’s role, responsibility center assignments, and dimension filters determine what data the API returns. Building this filtering into the API page ensures that integrations consuming the endpoint respect your organization’s security model without requiring external filtering logic that might go stale or be misconfigured.

Authentication Strategies and Secure Data Exposure

Business Central supports OAuth 2.0 authentication via Azure AD, which securely delegates user identity without transmitting credentials directly to applications. For third-party integrations, you register the consuming application as an Azure AD client, establish trust with Business Central’s tenant, and issue tokens that grant scoped access. This pattern scales across internal applications, third-party SaaS vendors, and mobile apps developed for field teams.

For service-to-service integrations where no interactive user exists, you configure Azure AD application permissions and use the client credentials flow. The integrating service authenticates with its own client ID and secret, receives a token, and calls the API with that token’s authority. Business Central’s authorization layer validates the token and enforces whatever permissions the service principal has been granted, typically read-only access to specific entities for automation scenarios.

The critical security decision is scope: never grant an external service permission to the entire database. An automated reconciliation job needs specific journal entries and GL accounts, not access to vendor master records or payroll data. Implement this by controlling AL permissions at the table and field level within your custom API page. The code executes in a specific permission context; requests outside that context return 403 Forbidden before any data is retrieved.

Rate limiting prevents external integrations from accidentally or intentionally overwhelming Business Central with requests. Implement this at the API page level using a simple counter in a temporary table or in-memory structure, logging the authenticated user’s token, timestamp, and request count, then rejecting requests that exceed a threshold (for example, 100 requests per minute per token). This protects the system during integration testing when third-party code loops unexpectedly, or when a mobile app’s sync logic gets into a retry storm during network instability.

Common Integration Patterns and Implementation Considerations

Third-party integrations typically follow one of three patterns: read-heavy analytics flows that pull data into external dashboards or reporting systems, transaction-driven flows that insert or update Business Central records from external sources, and bidirectional syncs where data changes in either system trigger updates in the other.

For read-heavy scenarios, design your custom API to return data in a shape optimized for the consuming tool. A data warehouse pulling nightly sales history might request summarized daily shipment totals grouped by customer and region, not individual line items. An analytics platform might want both transaction details and aggregated metrics in a single response. Custom API pages let you compute these shapes once in Business Central and deliver them efficiently.

For transaction-driven inbound flows, implement idempotency. When a third-party order management system sends a new sales order to Business Central, include a unique idempotency key. The API page stores this key with the created record; if the same request arrives again due to a network retry, the API recognizes the duplicate and returns the existing record without creating a second one.

Bidirectional syncs require careful conflict resolution. If Business Central and an external inventory system both allow updates to stock levels, and both systems update the value simultaneously, which one wins? Document your conflict resolution policy explicitly: last-write-wins, field-level rules, or row-level versioning. Without explicit conflict resolution, silent data loss occurs.

Performance Optimization and Monitoring at Scale

As external integrations scale, API performance directly affects integration throughput and cost. A slowly responding API forces external systems to maintain more concurrent connections and retry more frequently. Optimize by indexing the tables you query most frequently within the custom API, filtering at the database layer rather than in application logic, and using efficient AL patterns.

Monitor API health using Application Insights or similar observability tools. Instrument your custom API page to log request count, response time, error rates, and authenticated users consuming high volumes. This visibility lets you spot performance degradation early and make data-driven decisions about whether to add rate limits, optimize queries, or provision additional capacity.

Establish a communication channel with integrating partners about expected availability, planned maintenance windows, and API contract changes. When you modify a custom API’s response shape, maintain backward compatibility by supporting both versions for a defined sunset period.

Conclusion

Secure, maintainable API integration in Business Central demands intentional architecture. Custom API pages decouple your internal data model from external contracts, authentication flows enforce security boundaries, and thoughtful rate limiting and monitoring protect system stability. Organizations that treat API design as a core platform responsibility, document integration contracts clearly, and actively monitor consuming applications’ health build integration ecosystems that scale with business growth rather than becoming bottlenecks during busy cycles.


Routeget Technologies has architected integration frameworks for mid-market and enterprise Business Central deployments, helping organizations balance the flexibility of third-party ecosystems with the security and governance demands of regulated industries. Custom API design, Azure AD federation, and integration monitoring are core areas of our Dynamics and Power Platform consulting practice.

#DynamicsIntegration #BusinessCentralAPI #RESTfulArchitecture #MicroservicesPatterns #CloudIntegration #DataSecurityGovernance #EnterpriseIntegration #APIDesignBestPractices

Handling Long-Running Operations in Dataverse Plugins: Async Processing Patterns and Monitoring High-Volume Batch Jobs

Long-running operations in enterprise Dataverse environments present a consistent challenge. A synchronous bulk import that takes seven minutes blocks user interactions for seven minutes. A validation rule that queries 50,000 related records across subsidiaries times out and fails silently. A financial posting process that updates 15,000 journal entries locks user access and leaves the system unresponsive. These are not edge cases or theoretical problems; they represent the difference between a predictable, scalable system and one that feels brittle under actual load.

The Dataverse asynchronous (async) processing model exists precisely because these patterns are inevitable in real-world implementations. Understanding how async operations work, when to use them, and how to monitor them is foundational for any developer or architect responsible for high-volume or long-running data operations.

Why Synchronous Processing Fails Under Load

Synchronous operations execute immediately in the calling thread, blocking until completion. For most operations this is fine. A validation plugin that runs in 50 milliseconds does not materially impact the user. But three things happen when operations cross a certain time threshold.

First, the user’s request hangs. A form save takes seven seconds instead of 500 milliseconds. The application appears frozen. Users click buttons again. Connections time out. The business process stalls.

Second, database locks accumulate. Long-running updates hold locks on affected rows. Concurrent operations queue behind those locks. Queries that should take 100 milliseconds now take 10 seconds waiting for locks to release. The performance problem cascades across unrelated processes.

Third, plugin execution context serializes certain operations, creating bottlenecks. The platform enforces timeout limits on synchronous plugin execution (two minutes in most configurations). Exceed that, and the operation fails. If your plugin processes 10,000 records per invocation and crosses the timeout, the entire transaction rolls back. All work is lost, leaving data partially updated.

Asynchronous processing solves this by removing the blocking behavior entirely. Work is queued, executed in the background, and does not block the original request.

How Dataverse Async Operations Work

When an asynchronous plugin or workflow executes, Dataverse follows this sequence. First, the event occurs and synchronous plugins run to completion. Then, the platform serializes the execution context (the data object that the async operation will receive) and creates an AsyncOperation record in the system jobs table. That record enters a queue, ordered by creation date. When background resources become available, the system picks up the AsyncOperation record and executes the async plugin as if it were a synchronous operation running in its own isolated context.

This architecture has important implications. The async operation runs completely independently. It cannot see changes made to the original record after the async job was queued. It cannot throw exceptions that bubble up to the user; the user’s transaction completes successfully regardless of whether the async job eventually fails. If the async job fails, the failure does not cascade to the original data modification. The system records the failure in the AsyncOperation table, but the original record update stands.

The queue is first-in, first-out by default, but the system evaluates resource availability continuously. A job can remain suspended if the system is resource-constrained. High-volume environments often experience queuing, where jobs wait in the ready state until resources open up. Monitoring these queues is essential.

Async Patterns for High-Volume Operations

The dependency token pattern enables serialized async execution when order matters. Two async jobs created with the same DependencyToken value execute serially in creation order rather than in parallel. This is critical for scenarios where later work depends on earlier work completing first. For example, if job A aggregates revenue and job B calculates cost of goods sold based on job A’s output, assigning both the same dependency token ensures B waits for A.

However, dependency tokens apply only to async plugins that the system creates automatically. If your code manually enqueues async work, this mechanism does not apply. In those cases, custom coordination logic is necessary.

For truly high-volume scenarios, consider batching patterns. Instead of creating one async job per record, group records into batches and create one job per batch. A financial posting process might create one async job for every 500 journal entries rather than one job per entry. This reduces queue size, improves efficiency, and simplifies monitoring. The tradeoff is that if a batch fails, all records in that batch require reprocessing.

Monitoring AsyncOperation records is not optional in high-volume environments. Set up automated queries to track job states. Dataverse groups jobs into states: Ready (waiting for resources), Suspended (paused or waiting for dependencies), Locked (currently executing), and Completed (either succeeded, failed, or canceled). A consistently high count of jobs in the Ready or Suspended state signals a bottleneck. It may indicate that the system is resource-constrained, or that a particular job type is failing and retrying repeatedly.

Practical Monitoring Approach

Build queries that segment jobs by type, state, and duration. This query identifies workflow jobs stuck in progress:

GET /api/data/v9.2/asyncoperations?$filter=(operationtype eq 10 and statecode eq 2)&$select=asyncoperationid,name,createdon,startedon,executiontimespan,message&$orderby=createdon desc

Run this daily. Jobs that have been in progress for more than an hour warrant investigation. Long execution times indicate either genuine long-running work (which may be acceptable) or hung jobs that need manual intervention.

Similarly, track failed jobs:

GET /api/data/v9.2/asyncoperations?$filter=(statuscode eq 31)&$select=asyncoperationid,name,createdon,errorcode,message,friendlymessage&$orderby=createdon desc&$top=50

Group failures by error code. A spike in a particular error code indicates a systemic problem. For example, a sudden spike in error code 0x80040213 (record not found) suggests an upstream process is deleting records that dependent async jobs expect to exist.

Error Handling and Retry Logic

Dataverse retries failed async jobs automatically based on the operation type and error. By default, most operations retry up to three times. However, this default is not always optimal. A job that fails due to a transient network timeout may succeed on retry. A job that fails because of invalid business logic will fail on every retry, wasting resources.

Custom plugins can handle this differently. Dataverse serializes the exception information into the AsyncOperation table. Your monitoring logic can examine the error and decide whether to manually trigger a retry or escalate to a support queue. Do not rely on automatic retry for all scenarios; be intentional about which operations should retry and under what conditions.

The FriendlyMessage column provides user-readable error text. Use this for alerting and dashboards. The Message column contains technical detail. Both are valuable in troubleshooting.

Maintenance and Cleanup

Successful async operations remain in the AsyncOperation table indefinitely unless explicitly deleted. High-volume environments can accumulate millions of completed records. This creates three problems: table growth (performance degrades as the table scales), storage consumption, and noise in monitoring queries (finding actual failures becomes harder).

Implement scheduled bulk deletion jobs targeting successful operations older than a retention period. A typical retention policy keeps the last 30 days of successful jobs and deletes older records. Failed jobs are retained longer (90 days is common) so that patterns can be analyzed and root causes understood.

Register async plugins with automatic deletion enabled where the operation is fire-and-forget. This setting tells Dataverse to delete the AsyncOperation record as soon as the job completes successfully. This is safe when you do not need audit trails of the async work, but dangerous if you need to track whether the work occurred.

Conclusion

Long-running operations and high-volume data processing are not fringe use cases in enterprise Dynamics 365 environments; they are normal. Synchronous plugins work fine for most validation and update logic, but the moment you touch more than a few dozen records or engage in complex orchestration, the synchronous model breaks down.

Async operations move these workloads out of the user’s request path, allowing the application to remain responsive and scalable. But async brings complexity: you lose immediate feedback, you must monitor job states, and failures can be silent. The difference between systems that run reliably at scale and systems that collapse under load often comes down to whether developers understand async patterns and build proper monitoring from the start.

Start by identifying your long-running operations. Audit your plugin implementations for anything that touches more than a handful of records or makes multiple external calls. If you find them, plan to move that work async and set up monitoring before the load increases. Waiting until your system is slow is too late.

—

Handling high-volume Dataverse operations is a core competency for enterprise implementations. Routeget Technologies has guided developers and architects through async architecture decisions and implementation patterns on dozens of large-scale Dynamics 365 deployments. Understanding when to move to async, how to monitor effectively, and what patterns prevent cascading failures is what separates systems that scale cleanly from ones that collapse under load.

#DynamicsDataverse #AsyncPlugins #PluginDevelopment #DataverseArchitecture #D365DevOps #EnterpriseIntegration

Why Business Central Webhook Notifications Go Missing, and How to Architect Around It

Solutions architect reviewing a network integration dashboard representing Business Central webhook notifications

A Business Central integration that sailed through testing starts dropping records three weeks into production, and nobody can explain why until someone finally opens the subscription logs and finds a long trail of silent deletions. This is one of the more common failure patterns solution architects run into once Business Central webhook notifications move from a sandbox demo into a live integration carrying order confirmations, inventory adjustments, or approval status changes. The webhook model itself is well documented, but a handful of its operational rules are easy to miss during initial build-out, and missing them is exactly what causes integrations to degrade quietly instead of failing loudly where someone would notice.

The Handshake Most Implementations Get Half Right

Every Business Central webhook subscription begins with a validation handshake, and it is stricter than most developers expect on first pass. When a subscription is created against the v2.0 API, Business Central sends a request to the registered notification URL with a validationToken appended as a query parameter. The receiving endpoint has to echo that exact token back in the response body with an HTTP 200 status, and this is not a one-time formality. The same handshake fires again every time the subscription is renewed, which matters because Business Central webhook subscriptions expire after three days by design. There is no way to extend that window through configuration; the only supported pattern is issuing a PATCH request to the subscription resource before expiration, which triggers a fresh validation round trip.

Teams that build their receiver as a lightweight Azure Function often hit trouble here because of cold starts. If the function has scaled to zero and the handshake response takes more than a few seconds to come back, Business Central treats it as a failed validation and the subscription is never established or renewed. Keeping the receiver warm, or at minimum handling the validation response as the very first branch in the function before any other logic runs, removes most of the flakiness teams report during initial rollout.

Why Business Central Webhook Notifications Stop Coming, With No Error in Sight

The part of the design that catches experienced integration teams off guard is what happens after a notification actually fails to deliver. Business Central will retry a failed notification for up to 36 hours, but only if the subscriber’s endpoint responds with HTTP 408, 429, or something in the 5xx range. Any other response code, including a 400 or a 404 that might result from a temporary bug in the receiver’s routing logic, causes the subscription to be deleted immediately with no further retry and no separate alert. From the Business Central side, everything looks fine because the delivery attempt technically completed. From the integration’s side, notifications simply stop arriving for that entity, and unless someone is actively polling the subscriptions endpoint to check for unexpected deletions, the gap can persist for days before anyone notices missing sales order lines or unsynced customer records downstream.

This is the mechanism behind the scenario at the top of this article. A receiver that returns a 500 during a genuine outage is actually in a safer position than one that returns a 400 because of a malformed payload edge case, since the 500 at least buys 36 hours of retry attempts while the underlying issue gets fixed. Teams that want resilience here should treat any unhandled exception in the receiver as a 5xx rather than letting it bubble up as a generic error page, and should build a scheduled check that compares active subscriptions against the expected list so a silent deletion gets flagged rather than discovered by a downstream data gap weeks later.

Developer workstation showing an abstract data synchronization diagram representing a Business Central API integration

The Records Webhooks Quietly Refuse to Support

Before troubleshooting delivery issues, it is worth confirming the subscribed entity was ever eligible for webhooks in the first place, since Business Central will let a subscription be created against certain API pages that then never fire a single notification. Webhooks are not supported for API pages built on temporary tables, for pages where the OData key consists of more than one field or is otherwise composite, for system tables above table number 2000000000, for anything exposed as an API type query rather than an API type page, or for the Job Queue Entry table specifically. An API page built for a custom extension needs ODataKeyFields set to a single field, typically SystemId, or the subscription will appear to register successfully while silently never delivering anything.

Document-style entities carry their own nuance worth designing around. A change to a sales invoice line does not generate its own notification; instead, Business Central notifies against the parent sales invoice header when any of its lines change. Integrations that subscribe only at the header level and assume line-level granularity will miss the specific detail of what changed and need to follow up with a GET request against the full document to reconstruct it, which is standard practice anyway since webhook payloads never carry the actual record data, only a pointer to it and the type of change that occurred.

Batching, Delay, and the Events That Let You Tune Both

Business Central does not fire a notification the instant a record changes. There is a default 30 second delay built in after the first change to a watched entity, which exists to let a burst of related changes settle before anything is sent. If more than 1,000 records change within that delay window, Business Central collapses the individual notifications into a single collection notification rather than sending a thousand separate payloads, which is a sensible default for bulk imports but can surprise a downstream system expecting one notification per record during something like a large data migration or batch order release.

Both behaviors are adjustable for AL developers who need finer control. The delay interval can be modified through the OnGetDelayTime integration event in the API Webhook Notification Mgt. codeunit, and the threshold that triggers collection-style batching can be adjusted through OnGetMaxNumberOfNotifications in API Webhook Notification Send. Extensions that need near-real-time delivery for smaller entities, such as a status flag that a warehouse floor system needs to react to within a couple of seconds, are reasonable candidates for tightening the delay through these events; extensions dealing with high-volume header tables like sales lines are usually better served leaving the defaults alone and designing the receiver to handle collection notifications gracefully.

Designing for the Gaps: A Hybrid Pattern That Actually Holds Up

Given all of the above, the honest engineering position is that Business Central webhooks were never meant to be the sole source of truth for keeping two systems in sync, and Microsoft’s own documentation is candid that delivery is not guaranteed. Subscriptions can lapse from a missed renewal, a receiver bug can trigger an unannounced deletion, and network conditions on either side can drop a notification outright. The pattern that holds up in production combines the two mechanisms rather than choosing one: webhooks handle the fast path, giving near-real-time propagation for the vast majority of changes, while a scheduled reconciliation job, often running hourly or nightly depending on data volume, polls the same API endpoints using a last-modified timestamp filter to catch anything the webhook layer missed.

This is a small amount of extra engineering for a meaningful reliability gain, and it also solves the operational blind spot described earlier: the reconciliation job can double as the subscription health check, verifying that expected subscriptions still exist and re-registering anything that was unexpectedly deleted. Teams that skip this step tend to discover the gap only when a customer or finance team member notices a record that should have synced and did not, which is a far more expensive way to find the same bug.

What This Means for Your Next Integration Build

None of these behaviors are hidden or undocumented, but they are scattered across API reference pages that most teams read once during initial scoping and rarely revisit once the integration is live. The practical takeaway is to treat the three-day renewal window, the narrow retry code list, the unsupported table types, and the batching thresholds as design inputs from day one rather than issues to debug after the fact. At Routeget Technologies, the integrations that hold up longest in production are the ones where the reconciliation safety net was scoped alongside the webhook subscriptions from the start, not bolted on after the first silent data gap got noticed. Getting that sequencing right the first time is usually the difference between an integration the business trusts and one that quietly needs babysitting.


#BusinessCentralAPI #WebhookReliability #ERPIntegration #DynamicsBusinessCentral #ALDevelopment #EnterpriseIntegration