Start with ownership, not flows
Before anyone opens Power Automate, agree which system owns each piece of data. In most professional services firms the split is fairly natural. Sales owns the relationship: who the client is, who you talk to, and what you are pursuing. Business Central owns the money: credit terms, VAT, billing addresses, invoices and what has been paid. Problems start when both systems are allowed to edit the same field and the integration has to guess which change wins.
Write the ownership down as a field-level table, one row per field, with columns for the owning system, the direction of sync and the event that triggers it. It is a dull document and it is the most valuable thing you will produce. It also gives finance and the sales operations lead something concrete to sign off, which matters because they are the people who will notice when the data is wrong.
A common pattern for firms that bill by project or retainer is that an account is created in Sales, becomes a customer in Business Central only when there is something to bill, and from that point finance owns the commercial fields while sales keeps owning the contacts and the pipeline. A typical split looks like this.
- Sales owns account name, website, sector, relationship owner, contacts and their roles, and opportunities.
- Business Central owns customer number, payment terms, credit limit, VAT registration, posting groups, blocked status, invoices and balances.
- Billing address is shared under a rule: Sales proposes it when the customer is created, and finance owns it afterwards.
Decide how records find each other
Every sync depends on a reliable way of matching a record in one system to its partner in the other. Matching on name is not reliable. Two clients with similar names, a rebrand or a typo will eventually produce a duplicate customer or, worse, an update applied to the wrong one.
Store the partner's identifier on both sides if you can. On the Dataverse side this is straightforward: add a column to the account table for the Business Central customer's system ID, which is a GUID, and a second column for the customer number so people can read it. On the Business Central side, holding the Dataverse account ID means adding a field to the customer table with an AL extension, and if you want Power Automate to read and write that field you will usually need a custom API page as well, because the standard API pages expose standard fields. If you cannot take on an extension, keep the link one-sided in Dataverse and always look up from there.
Use the Business Central system ID as the key rather than the customer number. Numbers follow a series that finance may want to change, and occasionally get corrected. The system ID does not change for the life of the record.
Also decide which Business Central company each account belongs to. Firms with more than one legal entity often run a company per entity, and the Business Central connector asks for the environment and company on every action. Make the company a field on the account, or derive it from the business unit, rather than hard-coding it in the flow.
Mapping accounts and contacts
The account to customer mapping is where most of the effort goes. Build it as one flow, triggered by the Dataverse connector's trigger for when a row is added, modified or deleted, scoped to the account table and filtered to the columns you actually sync, so that a change to an unrelated field does not start a run. The flow checks whether the account already holds a Business Central system ID. If it does not, and the account meets your creation rule, the flow creates the customer and writes the ID back. If it does, the flow updates only the fields Sales owns.
Contacts need more thought. Business Central has its own contact table, separate from customers, with company and person contacts linked to customers through business relations. For most firms the people who need to exist in Business Central are the billing contacts, the ones invoices are addressed to, not everyone in the CRM. Mark those in Sales with a flag or a contact role and sync only them. Fewer records in the integration means fewer things to fail.
Watch the fields that look the same but are not. Country and region, currency, payment terms and salesperson are lookups to code tables in Business Central and are often free text or choice columns in Sales. Map them through a small reference table you control, held in Dataverse, rather than through nested conditions in the flow. When finance adds a new payment term, someone adds a row, and nobody has to edit a flow.
- Map country and region through ISO codes held in the reference table, not display names.
- Map Sales account owners to Business Central salesperson codes through a reference row keyed on the user.
- Leave posting groups to finance, either set from defaults in the reference table or completed by finance in a review step.
- Sync the billing address only, and leave site and delivery addresses in Sales unless something in finance needs them.
Opportunities, quotes and orders
Opportunities usually stay in Sales. Business Central does not need to know about work you might win, and pushing pipeline into finance creates noise. What finance needs is the moment the work becomes real, which in Sales terms is a won opportunity, an accepted quote or a submitted order, depending on how your firm sells.
Pick one event and build around it. For firms that issue formal quotes and orders, the cleanest trigger is usually the order: when a Sales order reaches the status you define as confirmed, the flow creates a sales order in Business Central against the linked customer, with lines mapped from the order products. For firms that sell time and materials engagements, the better target is often a project in Business Central, created from the won opportunity with the commercial detail completed by the project accountant.
Quotes are worth syncing only if Business Central is where the quote document is produced. If Sales produces the quote and the client accepts it there, keep quotes in Sales and sync from the order onwards. Syncing quotes in both directions is rarely worth the effort of keeping revisions aligned.
Order lines need products to have a home in both systems. Map Sales products to Business Central items or resources through the same reference table approach, keyed on the item or resource number. Then decide where the price comes from. For professional services it is usually the price agreed in Sales, written as the unit price on the Business Central line, with finance able to amend it before posting. Once the order exists, send information back rather than letting both sides edit it: the Business Central order number and status, and later the invoice number and amount, written to read-only fields in Sales so the account manager can see the work has been billed without asking finance.
Business rules that stop the sync fighting itself
Two-way syncs fail in a predictable way. System A updates system B, the trigger in B fires and updates A, and the records bounce back and forth until something throttles or breaks. Prevent this deliberately rather than hoping the comparisons work out.
Then write down the rules that decide whether a record should sync at all. A customer created for a prospect who never buys is a nuisance for finance. Typical creation rules are that the account has at least one won opportunity, has a billing contact and has a complete billing address. Put the rule in one place, ideally a formula or calculated column in Dataverse that the flow reads, so it can be changed without editing the flow.
Deletes deserve their own rule, and the rule is usually not to sync them. Business Central restricts deleting customers that have ledger entries, and you would not want a CRM tidy-up to remove a customer from finance anyway. Sync deactivation instead: when an account is deactivated in Sales, set the customer's blocked status to the value finance has chosen, and leave anything beyond that to finance.
- Run each direction under a dedicated identity: an application user in Dataverse and a dedicated integration identity in Business Central.
- Add a trigger condition that ignores changes made by the integration identity, so the flow's own writes do not start new runs.
- Compare before writing: if the target already holds the value, skip the update.
- Use the trigger's column filter so only changes to mapped columns start a run.
- Stamp a last synced date on both sides for diagnosis, rather than relying on it as a lock.
Error handling logged to Dataverse
A flow that fails silently is worse than no flow, because people stop checking the data and start trusting it. Every sync flow should log its outcome to a table you own, and someone should look at that table every working day.
Create a Dataverse table, called something like Integration log, with columns for the source system, target system, entity, source record ID, operation, status, error message, the payload sent, a retry count and a link to the flow run. Put it in the same solution as the flows so it moves with them between environments, and give it a view filtered to failures in the last day.
Inside each flow, wrap the work in a Scope action named Try, and add a second Scope named Catch, configured to run after Try has failed or timed out. The Catch scope writes a failed row to the log, taking the error detail from the result of the Try scope and building the run link from the workflow() expression. A third scope can write a success row. Log successes as well as failures for at least the first months, because 'did it sync?' is the question you will be asked most often, and a log row answers it faster than opening run history.
Separate errors that will clear on retry from errors a person has to fix. Throttling and temporary unavailability are worth retrying, so set a sensible retry policy on the connector actions and let the platform handle them. Validation errors, such as a missing mandatory field or a payment term that does not exist in Business Central, will fail every time. Log them with a distinct status and route them to whoever can fix the data, which is usually finance or sales operations rather than IT. A scheduled flow that summarises open failures by entity and sends them to the owner each morning is cheap insurance.
Testing, cutover and who owns what
Build in a development environment connected to a Business Central sandbox, never to production. A sandbox created as a copy of production gives you realistic customer records to test against. Use connection references and environment variables for the Business Central environment and company, so that when the solution moves on, test talks to the sandbox and production talks to production. Mix-ups here are how test orders end up in the live ledger.
Before go-live, deal with the data that already exists. Your clients are probably already in both systems and need linking before any flow runs, or the first update will create duplicates. Export both lists, match them with finance in a spreadsheet that a person reviews, and load the partner IDs into both systems. Only then switch the flows on, in order: accounts, then contacts, then orders.
Make sure every connection runs under the integration identity rather than a named employee. Flows that run under a person's account stop working when that person leaves, which is a poor way to discover the dependency. After go-live, ownership needs to be explicit.
- Sales operations owns account and contact data quality and the creation rule.
- Finance owns the reference tables for payment terms, posting defaults and items, and works the validation failures.
- The IT or Power Platform team owns the flows, connections, integration identity and log, and looks for patterns in failures.
- One named person, with a deputy, reviews the integration log every working day.
When the native integration is the better choice
Business Central has a built-in integration with Dynamics 365 Sales, set up from within Business Central and running through Dataverse. It couples records between the two systems, maps tables and fields through integration table mappings you can inspect and adjust, and runs synchronisation as scheduled jobs in the job queue, with its own pages for monitoring errors and coupling. It covers the core of what most firms need: accounts and customers, contacts, items and products, currencies, units of measure, salespeople and users, and sales order processing, along with surfacing some Business Central information in Sales.
If your requirement is close to that standard shape, start there. You get coupling, conflict handling and monitoring that Microsoft builds and maintains, and your team avoids owning a bespoke integration. Additional fields can be added to the mappings through an AL extension, so needing a few extra columns is not a reason to walk away from it.
Power Automate earns its place when the requirement departs from that shape: you want projects rather than sales orders created from a won opportunity, only some accounts should ever become customers, the logic spans other systems such as a document store or a time recording tool, or you want event-driven updates rather than scheduled jobs. It also suits firms that need a small one-way feed and do not want the wider footprint of the full integration.
Do not run both against the same tables. If the native integration owns accounts and customers, your flows should not also write them. A reasonable hybrid lets the native integration handle the standard entities and uses Power Automate for what it does not cover, with the boundary written into the ownership table you started with. Supported tables, prerequisites and setup steps for the native integration change over time, so confirm them against current Microsoft documentation before you decide.