> ## Documentation Index
> Fetch the complete documentation index at: https://docs.os.default.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Salesforce contact matching in Default

> How Default finds an existing Salesforce contact by email before it creates a lead, what to do on Match and No Match, and how Convert Lead merges a duplicate lead into that contact and its account.

Default matches Salesforce contacts inside a workflow. Before a form fill, API call, or webhook creates
a new Lead, a **Match Record** node looks for a Contact with the same email. When one exists, the
workflow works with that Contact and its Account instead of creating a duplicate Lead. When a Lead for
the same person already exists, **Convert Lead** can merge it into the Contact.

Matching is built from workflow nodes, so each rule can be tested and each decision shows in the run
logs. The nodes are documented in [CRM nodes](/workflows/steps-crm). For HubSpot, see
[HubSpot contact matching](/lead-routing/matching/hubspot-contacts).

## How Salesforce contact matching works in Default

A contact-first intake workflow in Default runs these steps:

1. **A trigger starts the run with the person's email.** Use **Form Submission**, **API**, or
   **Incoming Webhook**. See [Triggers](/workflows/triggers).
2. **Match Record looks for a Contact.** Set **Record** to **Contact** and match **Email** to the email
   from the trigger.
3. **Match: the person is already a Contact.** Later nodes use the Contact's ID, **Owner ID**, and
   **Account ID**. No Lead is created, and the Contact keeps its owner.
4. **No Match: look for a Lead.** A second **Match Record** node searches Leads by the same email.
5. **Create a Lead only when neither exists.** On the second **No Match**, a **Round-Robin** node picks
   an owner and **Create Record** creates the Lead.

Default does not merge duplicate records on its own. Matching decides which existing record the
workflow uses, and the only merge a workflow makes is a lead conversion you add with **Convert Lead**.

## Match options for Salesforce contacts

| Match on | How to set it up | When to use it |
| - | - | - |
| Email | **Match Record** with **Record** set to **Contact**, and **Email** **equals** the email from the trigger | The standard choice. An email identifies 1 person. |
| Name at a known Account | **First Name** **equals**, **Last Name** **equals**, and **Account ID** **equals** an Account ID from an earlier node | The request carries no email, and an earlier **Match Record** already found the Account. |
| Contacts at a matched Account | **Account ID** **equals** the Account ID from an earlier **Match Record** step, with tiebreakers to pick 1 | You need a Contact at the company, for example to notify its owner. |
| A known Contact ID | **Contact ID** **equals** an ID from the trigger or an earlier node | The form or API call already passes a Salesforce Contact ID. |
| Email, then update or create | **Create Record** with **Record Type** set to **Contact**, **Update on conflict** on, and **Match field** set to **Email** | You want 1 node that updates the Contact when exactly 1 has that email and creates it otherwise. |

**Match Details** lists the Contact fields that Salesforce lets you filter on, including custom
fields, and needs at least 1 condition before the workflow can be published.

**Update on conflict** behaves differently from **Match Record**:

* It looks for an exact match on the **Match field** value, and the **Match field** must also be in
  **Mapped Fields**.
* With exactly 1 match, it updates that record with the mapped fields. With none, it creates a
  record. With 2 or more, the node fails, because it cannot tell which record to update.
* It writes every mapped field that Salesforce lets it update, including **Owner ID**. Leave
  **Owner ID** out of **Mapped Fields** so an existing Contact keeps its owner.

## When several Salesforce contacts match

The same email can sit on more than 1 Contact. **Prioritize matched records by** picks 1 of them.
Select **Add tiebreaker**:

| Tiebreaker | What it does |
| - | - |
| **Most recently modified** | Prefers the Contact with the latest **Last Modified Date**. A good first rule for duplicates. |
| **Newest created** | Prefers the most recently created Contact. |
| **Oldest created** | Prefers the first Contact created, often the original record. |
| **Sort by a field…** | Sorts by any date or number field, **Newest** or **Oldest** for dates and **Highest** or **Lowest** for numbers. |
| **Prefer records where…** | Prefers the matched Contacts that meet your conditions, for example **Account ID** **is not NULL**. |

Rules run from top to bottom, and only when 2 or more Contacts match. A single matching Contact is
used as it is, so put a hard requirement in **Match Details** instead. **Prefer records where…**
keeps only the Contacts that meet it. When none of the remaining Contacts meets it, Default skips the
rule, keeps those Contacts for the next rules, and adds a warning to the run log. Without any rule,
Default takes the first Contact Salesforce returns, and that order is not defined.
See [Salesforce lead-to-account matching](/lead-routing/matching/salesforce-lead-to-account#when-several-salesforce-accounts-match)
for how rules combine.

## When no Salesforce contact matches

The node follows **No Match** when no Contact has the email. It also follows **No Match**, without
searching, when the email is empty for that run. The run log then shows a warning that the match
conditions could not be built because upstream data was missing.

Use **No Match** to check for a Lead before you create one:

* Add **Match Record** with **Record** set to **Lead**. Match **Email** to the same email, and add
  **Converted** **equals** `false` so a Lead that was already converted does not count.
* On its **Match** branch, leave the existing Lead and its owner alone.
* On its **No Match** branch, pick an owner with **Round-Robin** and create the Lead with
  **Create Record**.

If the email can be empty, add a **Multi-Branch** node before the first **Match Record** so a
submission without an email takes its own path instead of creating a Lead. See
[Logic and timing nodes](/workflows/steps-logic-timing#multi-branch).

**Match Record** and **Create Record** are separate steps. If 2 runs for the same email overlap, for
example 2 form fills a second apart, both can find no Contact or Lead and both create a Lead.
**Update on conflict** has the same gap. A Salesforce duplicate rule that blocks a second Lead with
the same email closes it: **Create Record** then logs a warning and continues without creating a
Lead. The step carries the existing Lead's ID only when Salesforce's response names the matching
record. Otherwise its ID is empty, and the warning says that creation was skipped. If later nodes
need the Lead, add a **Multi-Branch** after **Create Record** that checks whether its ID is empty, and
on that branch find the Lead with a **Match Record** on **Email**.

## Build a contact-first intake workflow

This example handles a demo request form. Known Contacts keep their owner and get a follow-up task.
New people become Leads owned by the next member of a queue named `Inbound Leads`.

<Steps>
  <Step title="Add the trigger">
    In **Workflows**, create a workflow and add the **Form Submission** trigger. Set
    **Connected Form** to your demo request form.
  </Step>

  <Step title="Match the Contact by email">
    Add **Match Record** from the **Records** section. Set **CRM** to **Salesforce** and **Record** to
    **Contact**. Under **Match Details**, select **Edit**, choose **Email** and **equals**, and pick the
    email from the form. Select **Save changes**.

    Under **Prioritize matched records by**, select **Add tiebreaker** and choose
    **Most recently modified**.
  </Step>

  <Step title="Follow up with the Contact's owner">
    On the **Match** branch, add **Create Activity**. Set **CRM** to **Salesforce** and **Type** to
    **Task**. Under **Fields**, map the subject and due date, set the Task owner to the Contact's
    **Owner ID** from the **Match Record** step, and set the Task's **Name ID** to the Contact's ID
    from the same step. Leave **Associated record (optional)** empty. It fills the Task's
    **Related To ID**, which takes a record such as an Account or Opportunity, not a Contact.
  </Step>

  <Step title="Look for an existing Lead">
    On the **No Match** branch, add a second **Match Record**. Set **Record** to **Lead**. Match
    **Email** **equals** the email from the form, and add **Converted** **equals** `false`. Leave its
    **Match** branch without an owner change.
  </Step>

  <Step title="Create the Lead">
    On the second **No Match** branch, add **Round-Robin** with **Queue** set to `Inbound Leads`. Then
    add **Create Record** with **Platform** set to **Salesforce** and **Record Type** set to **Lead**.
    In **Mapped Fields**, map **Email**, the fields your Leads require, such as **Last Name** and
    **Company**, and **Owner ID** set to **Latest assigned user** under **Round robin**.
  </Step>

  <Step title="Test and publish">
    Select **Test** and run the form trigger once with the email of an existing Contact and once with
    a new email. Check Salesforce after each run. Then select **Publish** and check that the status
    next to the [deployment switch](/workflows#the-deployment-switch) reads **Live**. A new workflow
    goes live when you first publish it. If the status reads **Paused**, turn the switch on.
  </Step>
</Steps>

<Warning>
  A workflow test performs real actions in Salesforce. Use test records.
</Warning>

To book the person with the Contact owner instead, add **Display Scheduler** on the **Match** branch,
turn off **Use event hosts**, and add the Contact's **Owner ID** from the **Match Record** step as the
host. See [Routing and scheduling nodes](/workflows/steps-routing-scheduling#display-scheduler).

## Merge a duplicate lead into the existing contact

When a new Lead has the same email as an existing Contact, **Convert Lead** can merge the Lead into
that Contact and its Account. Salesforce then keeps 1 person record instead of a Lead and a Contact.

<Steps>
  <Step title="Start from the new Lead">
    Add the **CRM Record Created** trigger with **CRM** set to **Salesforce** and **Record** set to
    **Lead**.
  </Step>

  <Step title="Match the Contact">
    Add **Match Record** with **Record** set to **Contact**, and match **Email** **equals** the Lead's
    **Email** from the trigger. Add the **Most recently modified** tiebreaker.
  </Step>

  <Step title="Convert the Lead into the Contact">
    On the **Match** branch, add **Convert Lead** and set the fields in the table below.
  </Step>

  <Step title="Route the rest">
    On the **No Match** branch, route the Lead as usual, for example with
    [lead-to-account matching](/lead-routing/matching/salesforce-lead-to-account).
  </Step>
</Steps>

| **Convert Lead** field | Value |
| - | - |
| **Lead to convert** | The Lead's record ID from the trigger. |
| **Contact to merge into (optional)** | The Contact's ID from the **Match Record** step. |
| **Account to merge into (optional)** | The same Contact's **Account ID** from the **Match Record** step. Salesforce merges a Lead into an existing Contact together with that Contact's Account, so set both. |
| **Connect Converted Lead to Opportunity** | **None**, unless every merged Lead starts a deal. |
| **Owner (Optional)** | Leave empty. Default sends an owner to Salesforce only when this field is set. |
| **Override LeadSource field** | Leave off to keep the Contact's existing source values. |
| **Send notification E-Mail** | Turn on only when the owner must receive Salesforce's conversion email. |

What to know before you convert:

* **Author Apex.** Default converts leads through Apex, so the Salesforce user connected to Default
  needs the **Author Apex** permission.
* **Duplicate rules apply.** A duplicate rule set to block stops the conversion and the node fails,
  with a message that suggests merging into the matching Account or Contact. A rule set to allow with
  an alert lets the conversion through.
* **Already converted.** A Lead that is already converted returns its existing IDs, and the node does
  not convert it again.
* **The Contact needs an Account.** Salesforce merges a Lead into an existing Contact only together
  with that Contact's Account. When the matched Contact has no Account, Default sends no Account ID
  and the conversion fails. Before **Convert Lead**, add a **Multi-Branch** that checks the Contact's
  **Account ID** is not NULL, and route Contacts without an Account another way.
* **Permanent.** Salesforce cannot undo a lead conversion. Test with a test Lead.

## Check and troubleshoot Salesforce contact matching

Open the workflow, select **Runs** at the top, and select a run. Each **Match Record** step shows its
conditions, how many records Salesforce returned, and the record it chose. See
[Run logs](/workflows/run-logs).

| What you see | What to check |
| - | - |
| Known people get a new Lead | Check that the first **Match Record** uses **Record** **Contact** and compares **Email** with the email from the trigger. |
| A new Lead is created although a Lead exists | Check the second **Match Record**. A Lead with **Converted** set to `true` does not count. |
| The wrong duplicate Contact is used | Add or reorder tiebreakers under **Prioritize matched records by**. |
| **Create Record** fails because several records match the match field | **Update on conflict** found more than 1 record with that value. Use **Match Record** with tiebreakers instead. |
| The run log says the match conditions could not be built because upstream data was missing | The email was empty for this run. |
| **Convert Lead** fails because the connected user needs the **Author Apex** permission | Give the Salesforce user connected to Default the **Author Apex** permission. |
| **Convert Lead** fails because Salesforce refused the conversion as a duplicate | Set **Contact to merge into (optional)** and **Account to merge into (optional)** to the existing records, or review the Salesforce duplicate rule. |

Related: [Lead routing software from Default](https://www.default.com/product/lead-routing-software)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.