> ## 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 account and HubSpot company matching in Default

> How Default matches a company-level signal, such as an Account Signal website visit or a webhook that carries only a domain, to a Salesforce Account or HubSpot Company by domain, and routes it to the matched record's owner.

Default matches company-level signals to Salesforce Accounts and HubSpot Companies inside a workflow.
When a signal names a company but no person, such as an **Account Signal** from a website visit or an
**Incoming Webhook** request that carries a domain, a **Match Record** node finds the Account or
Company by domain. Later nodes read the matched record's owner to notify them, or send an account
without a current owner to a queue.

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 signals that carry a person's
email, see [Salesforce lead-to-account matching](/lead-routing/matching/salesforce-lead-to-account) and
[HubSpot contact-to-company matching](/lead-routing/matching/hubspot-contacts-to-companies).

## Signals that start a company match

| Signal | Trigger setup | Company data it gives later nodes |
| - | - | - |
| A company visits your website in real time | **Website Intent Signal**, added as **Account Signal**. This sets **Trigger type** to **Real-time company**. Optionally set **Tracked domain** and **Page URL pattern**. | **Domain** and other company fields under **Identified Company**, from Clearbit Reveal or Demandbase. The page URL, IP address, and session ID under **Intent data**. |
| A company is identified in batches | **Website Intent Signal** with **Trigger type** set to **Batched company (Vector)**. | The company's domain, name, LinkedIn URL, and size under **Identified Company**, plus the page and UTM values under **Intent data**. |
| Another system sends a company | **Incoming Webhook** with a **Connected Webhook**. | The fields of the request body, for example a `domain` field your system sends. |

What each signal needs to know:

* **Account Signal** runs only on pages where **Reveal** is on, and only when the provider returns a
  company domain. The same workflow starts at most once per company in an hour.
  See [Visitor De-anonymization](/forms/reveal).
* **Batched company (Vector)** fires only when Vector identifies the company with a verifiable
  domain, about every 30 minutes.
* The **API** trigger always takes a person's email, so use **Incoming Webhook** for a signal that
  carries only a company.

## How company matching works in Default

A company matching workflow in Default runs these steps:

1. **The trigger supplies a domain.** It comes from **Identified Company** or from the webhook's
   request body.
2. **Match Record searches by domain.** Set **Record** to **Account** for Salesforce or **Company**
   for HubSpot, and compare the website or domain field with the signal's domain using
   **domain matches**.
3. **Tiebreakers pick 1 record.** When several records share the domain, the rules under
   **Prioritize matched records by** choose 1.
4. **The node branches.** **Match** carries the record's ID and fields, including its owner.
   **No Match** runs when no record qualifies.
5. **Later nodes route the signal.** Notify the owner, send an account without a current owner to a
   queue, or post unmatched companies to a channel.

## Match on the domain

| CRM | **Record** | Condition in **Match Details** |
| - | - | - |
| Salesforce | **Account** | **Website** **domain matches** the signal's domain |
| HubSpot | **Company** | **Company Domain Name** **domain matches** the signal's domain |

**domain matches** reduces both sides to the company's domain before it compares them. A website
stored as `https://www.hp.com/en-us` or `store.hp.com` matches `hp.com`, and a lookalike such as
`companyhp.com` does not. See
[Match an account by company domain](/workflows/steps-crm#match-an-account-by-company-domain) for the
full comparison table.

Best practices for matching a company by domain:

* Use **domain matches** instead of **contains string**. **contains string** compares raw text, so
  `hp.com` also matches `companyhp.com`, and a value such as `eu.hp.com` misses a website stored as
  `https://www.hp.com`.
* Pass the domain as it arrives. The signal's domain needs no cleanup, because the operator reduces it
  the same way as the CRM field.
* Join a second condition with **or** when your records keep domains in more than 1 field, for example
  **Website** and a custom domain field.
* Skip Salesforce formulas or flows that write a cleaned domain into a custom field for matching. The
  operator cleans both sides at run time.

**Match Details** needs at least 1 condition before the workflow can be published.

## When several accounts or companies match

A parent company and its subsidiaries, or duplicate records, can share a domain. Add rules under
**Prioritize matched records by** so the node picks the right one on purpose:

| Tiebreaker | What it does |
| - | - |
| **Largest by employees** | Prefers the record with the most employees. |
| **Most recently modified** | Prefers the record modified most recently. A good second rule. |
| **Oldest created** | Prefers the first record created, often the original. |
| **Has a parent account** (Salesforce) or **Has a parent company** (HubSpot) | Prefers records that have a parent. |
| **Prefer records where…** | Prefers the matched records that meet your conditions. |

Rules run from top to bottom, and only when 2 or more records match. A single matching record is
used as it is, so put a hard requirement, such as an owner who is a member of a Default queue, in
**Match Details** instead. A filter rule keeps only the records that meet it. When none of the
remaining records meets it, Default skips the rule, keeps those records for the next rules, and adds
a warning to the run log. Tiebreakers never send the node to **No Match**. Without any rule, Default
takes the first record the CRM returns, and that order is not defined. See
[When several Salesforce accounts match](/lead-routing/matching/salesforce-lead-to-account#when-several-salesforce-accounts-match)
for every tiebreaker and how they combine.

## When no account or company matches

The node follows **No Match** in these cases:

| Case | What happens |
| - | - |
| No record has the signal's domain | The search returns nothing. |
| The domain is empty for this run, for example a webhook request without it | Default does not search. The run log warns that the match conditions could not be built because upstream data was missing. |

What to do on **No Match**:

* **Notify a channel.** Add **Send Slack Message** with a channel under **Slack channels**, and put the
  domain and page in the **Message**. Someone on the team decides whether the company belongs in the
  CRM.
* **Create a record only if your team wants that.** A **Create Record** node can add the Account or
  Company with its name and domain. Every identified company then lands in your CRM, so add a
  **Multi-Branch** condition first, for example on company size, when only some companies qualify.

## Route the signal to the matched record's owner

On the **Match** branch, the matched record carries its owner: **Owner ID** on a Salesforce Account,
**Company owner** on a HubSpot Company. Later nodes can use it in 3 ways:

| Goal | Nodes | Setup |
| - | - | - |
| Notify the owner | **Send Slack Message** | Under **Recipients**, choose the owner field from the **Match Record** step. Default turns the CRM owner into their Default user and sends a direct message. |
| Tell the owner and the team | **Send Slack Message** | Pick a channel under **Slack channels**, and type `@` in **Message** to mention the owner from the **Match Record** step. |
| Assign an account that has no current owner | **Multi-Branch**, **Round-Robin**, **Update Record** | Add a **Multi-Branch** branch whose condition matches only the accounts to reassign. For HubSpot, use **Company owner** **is NULL**. Salesforce requires an owner on every Account, so use **Owner ID** **equals** the User ID that holds unowned Accounts, such as an integration user, entered as a **Custom value**. Join each further owner, such as a departed rep, with **or**. On that branch, pick a member with **Round-Robin**, then set the owner field with **Update Record** to **Latest assigned user**. Leave **Else** empty. |

The owner needs a Salesforce User ID or HubSpot Owner ID mapping and a Slack User ID mapping in
[**Settings → Users**](/settings/users) for the Slack message to reach them.

Assign an owner only to records your team treats as unowned, for example accounts with no owner or
still owned by a departed rep. Name those owners in the branch condition and leave **Else** empty, so
every other account keeps its owner. A condition such as "the owner is not in the account
executives' queue" also catches accounts owned by a current rep outside that queue, and the
**Round-Robin** path would overwrite them. An owner written from a **Round-Robin** node counts as an
assignment for that queue.

## Build a workflow that alerts the account owner about a pricing page visit

This example uses Salesforce. When a company visits your pricing page, the Account owner gets a Slack
message. Companies without an Account go to a channel.

<Steps>
  <Step title="Add the trigger">
    In **Workflows**, create a workflow and add **Account Signal** from the trigger picker. Set
    **Page URL pattern** to `/pricing`. Make sure **Reveal** is on for that page.
  </Step>

  <Step title="Match the Account by domain">
    Add **Match Record** from the **Records** section. Set **CRM** to **Salesforce** and **Record** to
    **Account**. Under **Match Details**, select **Edit**, choose **Website** and **domain matches**,
    and pick **Domain** under **Identified Company**. Select **Save changes**.
  </Step>

  <Step title="Add tiebreakers">
    Under **Prioritize matched records by**, select **Add tiebreaker** and choose
    **Largest by employees**. Add **Most recently modified** below it.
  </Step>

  <Step title="Message the Account owner">
    On the **Match** branch, add **Send Slack Message**. Under **Recipients**, choose the Account's
    **Owner ID** from the **Match Record** step. In **Message**, insert the company name and the page
    URL with `{{`.
  </Step>

  <Step title="Post unmatched companies to a channel">
    On the **No Match** branch, add **Send Slack Message** with a channel such as `#website-visitors`
    under **Slack channels**, and insert the company's domain in **Message**.
  </Step>

  <Step title="Publish the workflow">
    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. Reveal only starts published
    workflows.
  </Step>
</Steps>

For HubSpot, set **CRM** to **HubSpot** and **Record** to **Company**, compare **Company Domain Name**,
and choose the Company's **Company owner** as the Slack recipient.

## Check and troubleshoot company matching

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

| What you see | What to check |
| - | - |
| The workflow does not start from a website visit | Check that **Reveal** is on for the page, the workflow's status reads **Live**, and **Tracked domain** and **Page URL pattern** match the visit. See [Visitor De-anonymization](/forms/reveal). |
| Visits from a company follow **No Match** although the record exists | Check the record's **Website** or **Company Domain Name** value and the conditions in **Match Details**. |
| The wrong record is chosen | Add or reorder tiebreakers under **Prioritize matched records by**. |
| The run log warns that a Prefer rule matched none of the remaining candidates and was skipped | None of the matched records met that filter rule, so the next rules chose the record. Fix the rule's condition, or move it to **Match Details** when it is a hard requirement. |
| The run log says the match conditions could not be built because upstream data was missing | The domain was empty for this run. For a webhook, check the field name in the request body. |
| The owner gets no Slack message | Map the owner's CRM user and Slack User ID in **Settings → Users**. |

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.