The identifier that changed when the org did

The worst Monday I have seen in a sales operations team started with an org consolidation that went to plan. Two Sales Cloud orgs became one and the migration ran clean. What nobody had checked was a nightly sync from Dynamics 365 Sales that matched accounts on the Salesforce record ID, stored in a text column on the Dataverse account table.

Every record in the new org had a new ID. The sync looked up eighteen thousand accounts by an identifier that no longer existed, matched none, and created a fresh copy of each. By the time someone paused the flow, reps had logged calls against both copies and the pipeline report had doubled. Working out which copy was real took three weeks, because the only shared identifier had been the thing that changed.

A record ID is a database key. It is issued by one environment and means nothing outside it. The identifier you carry between systems has to belong to the business object, be issued by the team that owns that object, and survive a rebuild, a sandbox refresh, or an acquisition on either side.

Ask what number finance reads out on the phone

When a team asks me which field to match on, I ask what number finance reads out when they ring a rep about an overdue invoice. That number is the candidate business key. Usually it is a customer number issued by billing, sometimes a contract reference or a site code. Nobody has ever read a GUID over the phone.

Then I put the candidate through three checks. One system issues it and every other system copies it. It exists before any downstream system needs the record, or there is an agreed way to hold a record that has none yet. And it is never reissued, which rules out customer numbers that billing recycles after an archive run. A candidate that fails the third check is a data quality project, and the sync should wait for it.

The owner matters as much as the number. In practice the owner of the customer key is whoever bills, because they lose money when it is wrong. If sales and finance have never agreed on that, the sync is the wrong thing to build first. A shared customer record needs the same rules on both sides before it needs the same screens.

Let both apps keep their own primary keys

Dataverse rows carry GUIDs. Salesforce records carry their own IDs. Leave both where they are. Give each side the business key as a real column, with uniqueness enforced by the platform rather than by a flow.

On the Dataverse side that is an alternate key on the account table, built from the customer number column, or from company code plus customer number when the number is only unique within one legal entity. An upsert can then address the row by that value, and a second row with the same number is rejected by the platform. On the Sales Cloud side it is an External ID field on Account, marked unique, so an upsert by that value works and a duplicate insert fails instead of creating a twin.

The matching rule then fits in one sentence a support analyst can repeat. An Account in Sales Cloud and an account row in Dataverse are the same customer when the customer number is equal. No name matching, no address scoring, no guessing from a postcode. If you find yourself writing a similarity score inside the integration, stop. That is stewardship work and it belongs in a queue with a person at the end.

The key rule is the first line of a small integration contract. Payload fields can come and go between versions. If the key rule changes, that is a new contract, and every consumer needs to hear about it before it ships.

Test the missing key, the repeated key, and the near twin

Before go-live I want three records pushed through the integration, each failing in a way someone can explain. The first record has no key. This is routine, because a rep creates a prospect in Sales Cloud weeks before billing issues a customer number. An upsert on a blank External ID either errors or inserts a fresh record every run, and only one of those outcomes gets anyone's attention.

The second record carries a key that already belongs to a different account. The common cause in Sales Cloud is a merge. A rep merges two accounts, the survivor keeps one External ID value, and the loser goes to the recycle bin with its number. On the next run Dataverse sends the lost number, finds no match, and recreates the account the rep just merged away.

The third record is the near twin. Two accounts with the same trading name, two sites, two customer numbers. A name rule merges them. A business key rule keeps them apart, which is correct, and then the reporting team asks why the group total is split across two rows. That question belongs to whoever owns the customer hierarchy, and the integration should hand it over rather than resolve it by accident.

Each failure points at a missing control. A missing key needs a holding rule or a review queue. A repeated key needs a source-of-record rule and a field-level lock on the External ID so nobody edits it by hand. A near twin needs a parent key alongside the site key and a steward who can say which applies. A blank field is a support signal, and these tests show what it is signalling before a customer does.

Write down what the operator does when the key is disputed

The key is working when the person on support at seven in the morning knows what to do with a record that arrived with no key or the wrong one. That is the test I apply in the design review. If the answer is that they raise a ticket to the integration team, then the integration team is the review queue, and they did not agree to that.

What I build instead is deliberately small. A table in Dataverse, or a list view on a status field in Salesforce, holding every record the sync could not match, with the reason and the raw key it received. A named data steward who clears that list by the end of each day. A source-of-record rule short enough to say out loud, such as billing wins on customer number and sales wins on everything else, so the steward can settle a conflict alone. It is the same discipline that goes missing when automation owners lose the thread.

In the meeting I say it plainly. If we cannot name the person who clears this queue, we are not ready to go live, because we would be going live with a queue nobody clears.

Keep the key rules next to the integration

The last thing I ask for is that the key rules live where a support analyst will look first, which is inside the integration itself. I have lost afternoons in a three-year-old project folder finding out which side issued the number, while the sync sat paused. Put the rule in the flow description or in the custom metadata record that the code already reads, anywhere the person diagnosing the failure already has open.

Keep it short. For each object that crosses the boundary, this is what I write down.

  • The business object and the team that owns it.
  • The key field on each side, by schema name, and which system issues the value.
  • What happens to a record that arrives without a key.
  • Which system wins when both sides hold a different value for the same field.
  • Who clears the unmatched queue and how they are reached.
  • The date the rule was last changed and who agreed to it.

Take one question into the next design review

Pick the object that crosses your boundary most often, nearly always the account, and ask what number the business reads out on the phone. Then ask whether either system stores that number in a field that enforces uniqueness. If the room goes quiet, you have found the work, and it costs far less to do now than the Monday after the next org consolidation.