ml-connector
Microsoft Dynamics 365 Business CentralCoupa

Microsoft Dynamics 365 Business Central and Coupa integration

Microsoft Dynamics 365 Business Central runs your finance, accounts payable, and general ledger. Coupa runs procurement and the supplier-facing invoice approval workflow. Connecting the two means an invoice that suppliers submit and your buyers approve in Coupa lands in Business Central accounts payable without re-keying, matched to its purchase order and vendor. Approved supplier invoices and purchase orders flow from Coupa into Business Central as draft purchase invoices and purchase orders, and once Business Central posts and pays them the paid status returns to Coupa so buyers see settlement. ml-connector handles the different auth and APIs on each side and moves records on a schedule you control.

How Microsoft Dynamics 365 Business Central works

Microsoft Dynamics 365 Business Central exposes vendors, purchase invoices, purchase orders, GL accounts, general ledger entries, dimensions, and payment journals through the Business Central API v2.0, a REST API built on OData v4 with JSON payloads. Authentication uses Microsoft Entra ID OAuth 2.0 client credentials (service-to-service), scoped to a tenant and a named environment such as production, so the base URL is tenant-specific and all company-scoped resources nest under companies({companyId}). Vendors and purchase invoices support writes, but a posted POST creates only a draft; the Microsoft.NAV.post bound action then posts it, and the chart of accounts and GL entries are read-only. Business Central can push change notifications by subscription, but notifications carry no payload, subscriptions expire every three days, and purchaseOrders is not webhook-supported, so finance records are typically read by polling with a lastModifiedDateTime filter and @odata.nextLink paging.

How Coupa works

Coupa exposes suppliers, purchase orders, approved supplier invoices, supplier invoice payments, and lookup values through its REST API as JSON. Every call authenticates with an OAuth 2.0 client-credentials token obtained from the instance oauth2/token endpoint, scoped to the operations granted, and each request carries an X-Coupa-API-Version header that pins the API release. The invoices endpoint can be filtered to approved status so only buyer-approved documents are read. Coupa also fires outbound webhooks for invoice, purchase order, supplier, and payment events, each signed with an HMAC SHA256 value in the X-Coupa-Signature header.

What moves between them

The main flow runs from Coupa into Microsoft Dynamics 365 Business Central. ml-connector reads approved supplier invoices from Coupa and posts them into Business Central as purchase invoices against the matching vendor, carrying the vendor reference, currency, amounts, and line detail, and it brings Coupa purchase orders into Business Central as purchase orders so receipts and bills can match. Suppliers are aligned so every Coupa invoice resolves to an existing Business Central vendor, and GL accounts and dimensions are mapped so each line codes to a valid account and cost dimension. After Business Central posts and pays a purchase invoice, the paid status returns to Coupa as a supplier invoice payment so buyers see settlement. Business Central stays the system of record for AP and the general ledger, so ml-connector posts financial documents only into Business Central and never writes GL entries into Coupa.

How ml-connector handles it

ml-connector stores both credential sets encrypted. On the Business Central side it requests an Entra ID OAuth 2.0 client-credentials token for the api.businesscentral.dynamics.com/.default scope, builds the base URL from the configured tenant and environment name, scopes every call under the right company, and refreshes the token before expiry. On the Coupa side it requests a client-credentials token from the instance oauth2/token endpoint, caches it per instance and client until shortly before expiry, refreshes once on a 401, and sends the X-Coupa-API-Version header on every call. It reads the Coupa invoices endpoint filtered to approved status so unapproved documents never reach AP. Suppliers, GL accounts, and dimensions are mapped first so each invoice posts to an existing vendor and valid account. A Coupa invoice arrives as a draft purchase invoice in Business Central, then the Microsoft.NAV.post bound action posts it, since a plain POST never auto-posts. Inbound Coupa webhooks are accepted only after the X-Coupa-Signature HMAC is verified, and because Business Central notifications carry no data and purchaseOrders is not webhook-supported, the connector polls Business Central on lastModifiedDateTime with a stored high-water mark and pages through @odata.nextLink continuation tokens. Business Central has no idempotency-key header, so ml-connector dedupes on the vendor and document number plus a BullMQ jobId, while Coupa rate-limits with HTTP 429 and Business Central caps at 600 requests per minute and returns 429 when throttled, so ml-connector backs off with jitter and retries, and every record carries a full audit trail and can be replayed if a downstream call fails.

A real-world example

A mid-sized facilities services company with roughly 400 employees runs Microsoft Dynamics 365 Business Central for finance and accounts payable and uses Coupa for procurement and invoice approvals across several regional branches. Before the integration, the AP team pulled approved invoices out of Coupa and keyed them into Business Central as draft purchase invoices one by one, then later went back into Coupa to mark which ones had been paid, which delayed month-end close and left suppliers asking about payment status. With Microsoft Dynamics 365 Business Central and Coupa connected, each approved Coupa invoice posts into Business Central AP automatically against its vendor and purchase order, gets posted through the Microsoft.NAV.post action, and the paid status flows back to Coupa once Business Central settles the payment. The double entry is gone, invoices reach the ledger the same day they clear approval, and buyers see settlement without asking AP.

What you can do

  • Post approved Coupa supplier invoices into Microsoft Dynamics 365 Business Central as purchase invoices against the matching vendor and purchase order.
  • Bring Coupa purchase orders into Business Central so receipts and bills can match against them.
  • Return Business Central purchase invoice payment status to Coupa as supplier invoice payments so buyers see settlement.
  • Keep Coupa suppliers aligned with Business Central vendors and map GL accounts and dimensions so each line codes to a valid account.
  • Bridge Coupa OAuth client-credentials with Microsoft Entra ID client credentials, verify signed webhooks, and poll with retries and a full audit trail.

Questions

Which direction does data move between Microsoft Dynamics 365 Business Central and Coupa?
The main flow is Coupa into Business Central. Approved supplier invoices and purchase orders move from Coupa into Business Central as draft purchase invoices and purchase orders, while paid status returns to Coupa as supplier invoice payments. Business Central stays the system of record for accounts payable and the general ledger, so ml-connector writes financial documents only into Business Central and never posts GL entries into Coupa.
How does ml-connector handle Coupa OAuth and Business Central authentication?
It requests an OAuth 2.0 client-credentials token from the Coupa oauth2/token endpoint, caches it per instance until shortly before expiry, refreshes once on a 401, and sends the X-Coupa-API-Version header on every call. On the Business Central side it requests a Microsoft Entra ID client-credentials token for the api.businesscentral.dynamics.com/.default scope and builds the tenant-specific base URL from the stored environment name, since that name is part of the URL and differs per customer. Both tokens are refreshed before expiry.
Does the integration use webhooks or polling?
It uses both, matched to each system. ml-connector verifies the X-Coupa-Signature HMAC on inbound Coupa invoice, purchase order, and payment webhooks before acting. Business Central change notifications carry no payload and expire every three days, and purchaseOrders is not webhook-supported, so ml-connector polls Business Central on a lastModifiedDateTime filter with @odata.nextLink paging and dedupes on the document number and a jobId so a retried read never posts a duplicate purchase invoice.

Related integrations

Connect Microsoft Dynamics 365 Business Central and Coupa

Free to use. Add your credentials, ping your real systems, and see if we fit.

Get started