Bi-Directional CRM Sync from Dataverse
When a record is created or modified in Dataverse, upserts the corresponding Salesforce record by external ID, ensuring both systems stay in sync without creating duplicates.
Provided as-is, without warranty of any kind. Review and test each pattern in a non-production environment before deploying it to live automations. See our Terms.
Overview
This flow keeps Microsoft Dataverse and Salesforce in sync by upserting a Salesforce record whenever a matching Dataverse Contact is created or modified. Upserts are performed via Salesforce's External ID lookup so there are no duplicates and no fragile Dataverse-to-Salesforce ID mapping tables.
Use Case
When your sales team lives in Salesforce but operations, service, or another business function lives in Dataverse, both systems need a single record of truth for contacts. This pattern syncs contact data in real-time so changes in either system propagate automatically.
The flow is ideal for teams that:
- Bi-directional sync via External ID — no duplicate records
- Survives Dataverse-to-Salesforce ID remapping — no fragile GUID tables needed
- Real-time trigger — syncs within seconds of Dataverse change
- Field mapping fully env-var-driven — no flow edits for new field names
- Escape hatch for external ID mismatch — skips upsert if External ID is empty
Flow Architecture
When A Dataverse Contact Is Created Or Modified
TriggerWebhook trigger on contact entity (scope: Organisation, message: CreateOrUpdate).
4× parallel Initialize Variable
Initialize variableLoad env vars: DataverseSyncExternalIdField, SalesforceFirstNameField, SalesforceLastNameField, SalesforceEmailField.
Init varDataverseContactGuid
Initialize variableExtract GUID from trigger @odata.id.
Init varExternalIdValue
Initialize variableDynamically pull the field named by env var from trigger body.
Init varFirstName, varLastName, varEmail
Initialize variablePull trigger body fields with safe fallback (LastName defaults to 'Unknown').
Compose Sync Snapshot
ComposeBundle resolved values into JSON for run-history inspection.
Check If External Id Provided
If conditionBranch on whether varExternalIdValue is non-empty before upserting to Salesforce.
- PatchItemByExternalId (Salesforce) — Upsert Salesforce Contact by the configured External ID, mapping First Name, Last Name, and Email from the Dataverse trigger body.
Skip the upsert and surface a 'Missing External Id' message in run history.
Environment Variables
| Schema name | Type | Default | Description |
|---|---|---|---|
| flowlibs_DataverseSyncExternalIdField | String | <configure> | Logical name of the Dataverse Contact column whose value should be matched against the Salesforce External ID (e.g. emailaddress1, new_accountnumber). |
| flowlibs_SalesforceFirstNameField | String | <configure> | Dataverse field copied into Salesforce Contact.FirstName. |
| flowlibs_SalesforceLastNameField | String | <configure> | Dataverse field copied into Salesforce Contact.LastName (falls back to 'Unknown' when empty). |
| flowlibs_SalesforceEmailField | String | <configure> | Dataverse field copied into Salesforce Contact.Email. |
Connectors & Connections
| Connector | API name | Actions used |
|---|---|---|
| Microsoft Dataverse | shared_commondataserviceforapps | SubscribeWebhookTrigger (trigger — When a row is created or modified on the Contact table) |
| Salesforce | shared_salesforce | PatchItemByExternalId (Upsert Salesforce Contact by External ID) |
Note — All connections are referenced as solution connection references; the flow is portable between environments as long as a connection is mapped at import time.
Customization Guide
Almost every realistic variant of this flow can be implemented by changing environment variable values. A few cases require small edits inside the flow definition — those are called out explicitly below.
- Deploying to another environment
- Import the solution, update the four env vars to match your Dataverse and Salesforce field names, bind the Dataverse and Salesforce connection references, verify the Salesforce connection has the External_Id__c field defined on the Contact table, and turn the flow on.
- Re-target the External ID match
- Change flowlibs_DataverseSyncExternalIdField to point to a different Dataverse field (e.g. emailaddress2, new_accountnumber) to switch which Dataverse column is used for the Salesforce External ID lookup.
- Change the mapped fields
- The three flowlibs_Salesforce*Field env vars point to Dataverse columns. Swap them to push any text field into FirstName, LastName, or Email.
- Add more Salesforce columns
- Open the Upsert action and add slash-path parameters under item/. Pair each with another Initialize Variable reading the corresponding Dataverse field.
- Avoid echo loops
- If you build a Salesforce-to-Dataverse reverse flow, gate each direction with a 'last modified by' check so a sync round-trip doesn't retrigger.
Key Expressions
The flow is intentionally light on Power Fx / WDL gymnastics — the heaviest expressions are the branch-name concatenation and the approval outcome check. They are listed below in the order they appear in the flow.
EXPR.01Parse contact GUID from trigger
Extracts the Dataverse Contact GUID from the @odata.id returned by the trigger.
EXPR.02Dynamic field pull from trigger body
Reads whichever Dataverse column is named by the env var, without hard-coding the field name.
EXPR.03LastName fallback
Falls back to 'Unknown' when the configured Dataverse Last Name field is empty (Salesforce requires LastName).
EXPR.04Env var read
Standard pattern for reading a solution environment variable's current value.
EXPR.05Check if External ID provided
Guard expression on the If condition — only upserts when the External ID resolved to a non-empty value.
Comments
Sign in to join the conversation.
Sign inNo comments yet. Be the first to share your experience with this flow.