Architecture

Business Central SOAP Retirement in v29: Migrate Page Web Services to APIs

Version 29 removes SOAP endpoints on Microsoft pages in Business Central. Find the integrations that still call them with telemetry and move each one to API v2.0, a custom API or OData.

12 min read
On this page

In Business Central 2026 release wave 2 (version 29), you can no longer expose a Microsoft page, any page from an app whose publisher is Microsoft, as a SOAP web service, so integrations that call those endpoints stop working after the update. To fix them, find the calls in telemetry (events RT0008 with category SOAP and RT0053), then move each integration to the built-in API v2.0, a custom API page in your own extension, or an OData V4 unbound action for codeunit logic. Copying the page into a per-tenant extension and publishing the copy is a supported stopgap, not a long-term answer.

Who this is for and what you will have at the end

This guide is for Business Central developers, partners and integration owners whose middleware, scripts or third-party products talk to Business Central through SOAP URLs that contain /WS/. It is most urgent for anyone who disabled the feature key in versions 26 to 28 to keep SOAP running on standard pages such as Customer Card or Sales Order.

At the end you will have:

  • An inventory of every SOAP endpoint that is still called, with the AL object behind it.
  • A decision for each endpoint: standard API, custom API, OData unbound action or temporary page copy.
  • Rewritten calls that use OAuth and REST, verified in a sandbox on the new version.
  • A telemetry query that proves nothing calls the old endpoints any more.

What is changing and when

SOAP has been on the deprecation list for a long time. The timeline on Microsoft's deprecated features page is:

VersionReleaseChange
18.02021 release wave 1Warning when UI pages are exposed as SOAP endpoints; SOAP superseded by OData V4
24.02024 release wave 1Formal warning that SOAP on Microsoft pages will be removed in version 29
26.02025 release wave 1SOAP on Microsoft pages disabled by default, controlled by a feature key
29.02026 release wave 2Exposing a Microsoft page as a SOAP endpoint is no longer possible
30.02027 release wave 1Exposing a Microsoft page as an OData endpoint is no longer possible

The feature key is Feature: Disable SOAP web services on Microsoft UI pages on the Feature Management page. It's enabled by default from version 26, which blocks publishing Microsoft pages as SOAP. Disabling it re-enabled the old behavior as a bridge.

One inconsistency in the documentation is worth knowing about: the article about the feature key says support, and the key itself, will be removed in version 30.0, while the deprecated features list, the SOAP web services article and the publishing article all state version 29.0. Plan for version 29. Treat anything that still works after the update as borrowed time.

What is and isn't affected

  • Affected in v29: SOAP endpoints on pages from any app with publisher Microsoft, which includes Base Application, System Application and every first-party app.
  • Not removed yet, but deprecated: SOAP endpoints on pages in your own extensions, and SOAP codeunit web services. Microsoft's guidance for all SOAP is to move to OData V4 or, preferably, API pages and queries.
  • Next in line: OData endpoints on Microsoft pages, removed in version 30. If you're rewriting a SOAP integration, don't move it to an OData page endpoint on a Microsoft page; you would be migrating twice. The companion article on replacing OData page endpoints with custom API pages and queries covers that change.

Microsoft's reason is the same in every entry: a UI page isn't an API. Fields, parts and actions on a page change between releases without being treated as breaking changes, so any integration built on a page can break during an ordinary update.

Prerequisites

  • Telemetry. The environment, or your apps, must send telemetry to an Azure Application Insights resource so you can query web service calls. Without it you have to rely on the Web Services page and on asking integration owners, which misses callers nobody remembers.
  • A sandbox on the new version. Use a sandbox environment to rebuild and test each integration before the production update.
  • A Microsoft Entra app registration for OAuth. Business Central online accepts only OAuth for web services. If your SOAP clients still use a user account, use the move as the moment to switch to service-to-service authentication; the setup is described in Business Central OAuth 2.0 service-to-service setup.
  • AL development tools (Visual Studio Code with the AL Language extension) if any endpoint needs a custom API page or an unbound action.

Step 1: Inventory SOAP endpoints from telemetry

Business Central logs every incoming web service call as event RT0008. The category custom dimension holds the endpoint type: API, ODataV4, ODataV3 or SOAP. The following query summarizes SOAP calls over the last 30 days and shows which AL object and app sit behind each endpoint:

traces
| where timestamp > ago(30d)
| where customDimensions has "RT0008"
| where customDimensions.eventId == "RT0008"
| where customDimensions.category == "SOAP"
| extend endpoint = tostring(customDimensions.endpoint),
         alObjectType = tostring(customDimensions.alObjectType),
         alObjectId = tostring(customDimensions.alObjectId),
         alObjectName = tostring(customDimensions.alObjectName),
         extensionPublisher = tostring(customDimensions.extensionPublisher),
         environmentName = tostring(customDimensions.environmentName)
| summarize calls = count(), lastCall = max(timestamp)
    by environmentName, endpoint, alObjectType, alObjectId, alObjectName, extensionPublisher
| order by calls desc

From version 26, Business Central also logs RT0053, Deprecated endpoint called: {endpoint}, when an integration calls an endpoint that's marked for removal. Its deprecationMessage custom dimension explains why, with the documented example SOAP webservice on UI page by Microsoft publisher. If you disabled the feature key to keep these endpoints running, this event is the most direct list of what breaks in version 29:

traces
| where timestamp > ago(30d)
| where customDimensions has "RT0053"
| where customDimensions.eventId == "RT0053"
| extend endpoint = tostring(customDimensions.endpoint),
         alObjectId = tostring(customDimensions.alObjectId),
         alObjectName = tostring(customDimensions.alObjectName),
         companyName = tostring(customDimensions.companyName)
| summarize calls = count(), lastCall = max(timestamp) by endpoint, alObjectId, alObjectName, companyName
| order by calls desc

Identifying the caller

SOAP calls carry less diagnostic data than OData and API calls. Business Central doesn't log httpMethod, httpStatusCode, httpHeaders, diagnosticsMessage or failureReason for SOAP requests, so you can't use the user agent to find the client. Use the user_Id general dimension instead (logged on RT0008 from version 24.2, and on RT0053). It holds the user telemetry ID, which you can match to a user card to see which account each integration signs in as. Group by user_Id and endpoint to separate integrations that share a page.

Cross-check the result against the Web Services page. Each published SOAP service there has an object type, object ID and service name. Anything published but absent from telemetry over a full business cycle, including month-end and year-end, is a candidate for unpublishing rather than migration.

Step 2: Choose a target for each endpoint

What the SOAP service doesRecommended targetWhy
CRUD on customers, vendors, items, sales or purchase documents, journalsBuilt-in API v2.0No code, versioned, optimized for integrations, webhook-capable
Reads or writes fields that API v2.0 doesn't exposeCustom API page in your extensionStandard APIs can't be extended; you copy the logic into your own API
Joins several tables or aggregates data, read-onlyCustom API queryJoins and totals in one read-only endpoint
Calls a codeunit procedure (RPC style)OData V4 unbound action on the same codeunitDocumented SOAP-to-OData migration path for procedures
Posts or processes a documentAPI v2.0 bound action, or a bound action on your own pageFor example Microsoft.NAV.post on salesInvoices
You need more time and can't change the client yetCopy the page into a per-tenant extension and publish the copy as SOAPSupported by Microsoft, but still SOAP and still deprecated

The built-in API is always the first choice. Business Central online enables it by default, it needs no publishing on the Web Services page, and Microsoft recommends choosing the highest API version available. Use the API Overview page in Business Central to see every API page, API query and API codeunit published in the environment, including those from installed apps.

Mapping SOAP operations to REST

SOAP page services expose a fixed set of operations. Their REST equivalents on an API page are:

SOAP page operationAPI page equivalent
ReadGET .../{entitySetName}({id})
ReadMultiple with filters and setSizeGET .../{entitySetName}?$filter=... (server-driven paging)
Create, CreateMultiplePOST .../{entitySetName}, or a $batch request with up to 100 operations
Update, UpdateMultiplePATCH .../{entitySetName}({id}) with an If-Match header
DeleteDELETE .../{entitySetName}({id})
IsUpdated, GetRecIdFromKey, ReadByRecIdUse the id (SystemId) and the @odata.etag value instead of SOAP keys

The SOAP key is a string that combines the record with a timestamp. API pages identify records by id, which maps to the record's SystemId, an immutable GUID. Store that GUID in the external system rather than the record number, because numbers can be renamed.

Step 3: Rebuild the calls

Standard API v2.0

All endpoints share one base URL. Include the environment name, and optionally the tenant ID, then the API route:

https://api.businesscentral.dynamics.com/v2.0/{tenantId}/{environmentName}/api/v2.0/companies({companyId})/customers

A SOAP client that ran ReadMultiple on the Customer Card with a name filter becomes a single GET:

curl -H "Authorization: Bearer $TOKEN" \
  "https://api.businesscentral.dynamics.com/v2.0/$TENANT_ID/production/api/v2.0/companies($COMPANY_ID)/customers?\$filter=displayName eq 'Fabrikam'"

A SOAP client that created a sales invoice and then ran a posting codeunit becomes a POST to salesInvoices followed by the post bound action, which returns 204 No Content:

curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Length: 0" \
  "https://api.businesscentral.dynamics.com/v2.0/$TENANT_ID/production/api/v2.0/companies($COMPANY_ID)/salesInvoices($INVOICE_ID)/Microsoft.NAV.post"

Codeunit procedures as OData unbound actions

If your SOAP service exposes a codeunit, you can keep the AL logic and change only the protocol. Publish the codeunit on the Web Services page with object type Codeunit, then call each procedure with a POST to the service name and the procedure name joined by an underscore:

POST {baseUrl}/ODataV4/{serviceName}_{procedureName}?company={companyName|companyId}
Authorization: Bearer {token}
 
{
  "inputJson": "{\"str\":\"Hello world!\",\"confirm\":true}"
}

Parameters are sent as JSON properties named after the AL parameters, and the return value comes back in a value property. This path keeps the endpoint on your own codeunit, so it isn't affected by the Microsoft page removals.

Custom API pages

When the standard API lacks a field, build an API page in your extension with PageType = API, set APIPublisher, APIGroup and APIVersion, and use SystemId as ODataKeyFields. Standard API pages can't be extended, so a copy is the only way to add fields. The full walkthrough, including API queries and bound actions, is in building custom API pages and queries.

The page-copy stopgap

Microsoft's documented fallback for SOAP on a Microsoft page is to copy the page source into your own extension and publish the copy. The removal applies only to pages from apps with publisher Microsoft, so a copy in your per-tenant extension can still be published as SOAP. Use it only to buy time for a client you can't change before the update. Your copy becomes code you maintain against every future Base Application change, and SOAP itself is still on the removal list.

Verify before the production update

  1. Run the rebuilt integration against the sandbox on the new version and compare results with production: record counts, totals and any posted documents.
  2. Re-run the RT0008 query filtered on the sandbox and confirm the integration now logs category API or ODataV4 instead of SOAP.
  3. On the Web Services page, unpublish SOAP services that no longer have callers.
  4. In production, confirm that the RT0053 query returns no rows for a full business cycle before the version 29 update reaches the environment.
  5. Check throughput. API and OData calls have documented per-user limits (for example 6,000 requests in a 5-minute sliding window) and return 429 Too Many Requests when exceeded, so add retry logic with a back-off.

Troubleshooting

A SOAP integration stops working after the version 29 update. Check the object ID behind the service on the Web Services page; if it's a Microsoft page, that's the cause. In version 29 that isn't possible and the feature key no longer helps. Move to an API, or publish a copy of the page from your extension as a temporary fix.

404 Not Found on the new REST URL. The endpoint isn't in the metadata. Check the environment name, the API route (api/v2.0 for standard APIs, api/{publisher}/{group}/{version} for custom ones) and that your extension is installed in that environment.

401 with Authentication_InvalidCredentials and the message The server has rejected the client credentials. The token isn't accepted by Business Central. Check that the token was issued for https://api.businesscentral.dynamics.com/.default and that the Microsoft Entra application is registered and enabled in that environment.

Internal_CompanyNotFound. The request didn't identify a company. Add companies({id}) to the path or the company query parameter. Use the company ID rather than the name, because an administrator can rename a company.

BadRequest_InvalidToken or Request_EntityChanged on PATCH. BadRequest_InvalidToken (Could not validate the client concurrency token required by the service) means the If-Match header is missing or invalid. Request_EntityChanged (Another user has already changed the record) means the ETag you sent is out of date. In both cases read the record again and send its current @odata.etag value.

429 Too Many Requests after cutover. A SOAP client that looped through ReadMultiple pages often becomes a chatty REST client. Use $filter, $select and $expand to fetch less data per call, and spread heavy workloads across more than one service principal if the per-user limits are reached.

Checklist

  • RT0008 SOAP inventory and RT0053 deprecated-endpoint report exported for every environment.
  • Each endpoint mapped to API v2.0, a custom API page or query, an OData unbound action or a temporary page copy.
  • Clients moved to OAuth, ideally service-to-service with a dedicated Microsoft Entra application.
  • Rebuilt integrations tested in a sandbox on the new version, with telemetry showing API or ODataV4 calls.
  • Unused SOAP services unpublished.
  • No RT0053 events in production before the version 29 update.
  • Retry and throttling handling in place for the REST clients.

References

Questions people ask

Does Business Central version 29 remove all SOAP web services?

No. Version 29 removes the ability to expose Microsoft pages, meaning pages from apps published by Microsoft, as SOAP endpoints. SOAP on your own extension pages and on codeunits still works, but SOAP as a whole is deprecated and Microsoft says it will be removed in an upcoming release.

How do I find which SOAP endpoints my integrations still call?

Connect the environment to Application Insights and query the RT0008 web service trace with category SOAP, and the RT0053 Deprecated endpoint called event, which Business Central logs from version 26 when a deprecated endpoint such as SOAP on a Microsoft UI page is called.

Can I turn SOAP on Microsoft pages back on after version 29?

Not with the feature key. In versions 26 to 28 you could disable the Disable SOAP web services on Microsoft UI pages key in Feature Management to keep these endpoints working. In version 29 the capability is removed; the documented fallback is to copy the page into your own extension and publish that copy.

What replaces a SOAP codeunit web service?

Publish the same codeunit for OData V4 and call each procedure as an unbound action with a POST to ODataV4/{serviceName}_{procedureName}. Microsoft documents this pattern specifically as a way to migrate from SOAP to OData.

Business CentralSOAPREST API v2.0ODataApplication Insights
  1. Business Central API Webhooks: Create, Validate and Renew Subscriptions

    Receive Business Central change notifications instead of polling: build a validating receiver in Azure Functions, create a subscription, process notifications and renew before the three-day expiry.

    Architecture10 min read
  2. Connect Business Central to Outlook, Teams and SharePoint in Microsoft 365

    Set up Business Central email, the Outlook add-in, the Teams app, read-only access with Microsoft 365 licenses, OneDrive, and SharePoint storage for document attachments.

    Architecture13 min read
  3. 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