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:
| Version | Release | Change |
|---|---|---|
| 18.0 | 2021 release wave 1 | Warning when UI pages are exposed as SOAP endpoints; SOAP superseded by OData V4 |
| 24.0 | 2024 release wave 1 | Formal warning that SOAP on Microsoft pages will be removed in version 29 |
| 26.0 | 2025 release wave 1 | SOAP on Microsoft pages disabled by default, controlled by a feature key |
| 29.0 | 2026 release wave 2 | Exposing a Microsoft page as a SOAP endpoint is no longer possible |
| 30.0 | 2027 release wave 1 | Exposing 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 descFrom 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 descIdentifying 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 does | Recommended target | Why |
|---|---|---|
| CRUD on customers, vendors, items, sales or purchase documents, journals | Built-in API v2.0 | No code, versioned, optimized for integrations, webhook-capable |
| Reads or writes fields that API v2.0 doesn't expose | Custom API page in your extension | Standard APIs can't be extended; you copy the logic into your own API |
| Joins several tables or aggregates data, read-only | Custom API query | Joins and totals in one read-only endpoint |
| Calls a codeunit procedure (RPC style) | OData V4 unbound action on the same codeunit | Documented SOAP-to-OData migration path for procedures |
| Posts or processes a document | API v2.0 bound action, or a bound action on your own page | For example Microsoft.NAV.post on salesInvoices |
| You need more time and can't change the client yet | Copy the page into a per-tenant extension and publish the copy as SOAP | Supported 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 operation | API page equivalent |
|---|---|
Read | GET .../{entitySetName}({id}) |
ReadMultiple with filters and setSize | GET .../{entitySetName}?$filter=... (server-driven paging) |
Create, CreateMultiple | POST .../{entitySetName}, or a $batch request with up to 100 operations |
Update, UpdateMultiple | PATCH .../{entitySetName}({id}) with an If-Match header |
Delete | DELETE .../{entitySetName}({id}) |
IsUpdated, GetRecIdFromKey, ReadByRecId | Use 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})/customersA 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
- Run the rebuilt integration against the sandbox on the new version and compare results with production: record counts, totals and any posted documents.
- Re-run the RT0008 query filtered on the sandbox and confirm the integration now logs
categoryAPIorODataV4instead ofSOAP. - On the Web Services page, unpublish SOAP services that no longer have callers.
- In production, confirm that the RT0053 query returns no rows for a full business cycle before the version 29 update reaches the environment.
- 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 Requestswhen 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
- Deprecated features in the client, server, database
- Disable SOAP web services on Microsoft UI pages feature key
- SOAP web services
- Basic page operations
- Web service request trace (telemetry)
- Publish a web service
- REST API web services
- API endpoint structure
- salesInvoice resource type
- Creating and interacting with an OData V4 unbound action
- Troubleshooting REST API/OData calls
- Operational limits in Business Central online