> ## 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.

# Routing product-qualified leads (PQLs) to sales with Default

> How to route product-qualified leads (PQLs) to sales with Default: product events from Segment, PostHog, Mixpanel, Amplitude, Hightouch, or Census start a workflow through the Incoming Webhook or API trigger, then Match Record, Enrich Data, Multi-Branch or AI Prompt, Round-Robin, Create Record, Update Record, and Send Slack Message qualify the user and assign a rep in Salesforce or HubSpot.

A product-qualified lead (PQL) is a user whose product usage signals buying intent, such as signing
up, inviting teammates, or reaching a usage threshold. A marketing-qualified lead (MQL) qualifies on
marketing activity, such as a form fill or a content download, while a PQL qualifies on what the
person does in your product. Product-led sales teams route PQLs to a rep while the usage is fresh.

Default routes PQLs with 1 workflow. A product event reaches Default through the
**Incoming Webhook** or **API** trigger, the workflow checks Salesforce or HubSpot for the person,
enriches them, qualifies them on usage and fit, and gives them to the account owner or the next rep
in a queue. The rep gets the record in the CRM and a message in Slack.

## The PQL workflow at a glance

| Step | Node | What it does |
| - | - | - |
| 1. Receive the event | **Incoming Webhook** or **API** trigger | Starts a run for each product event and maps the event to a person by email. |
| 2. Check the signal | **Multi-Branch** | Keeps events that meet your usage rules, from fields in the event itself. |
| 3. Check the CRM | **Match Record** | Finds an existing Lead or Contact, so known people keep their owner. |
| 4. Enrich | **Enrich Data** | Adds company size, industry, and title to new people. |
| 5. Qualify on fit | **Multi-Branch** or **AI Prompt** | Keeps users whose company fits your sales motion. |
| 6. Assign | **Match Record** and **Round-Robin** | Picks the account owner when the company exists, or the next rep in a queue. |
| 7. Hand off | **Create Record**, **Update Record**, **Send Slack Message** | Writes the owner to the CRM and tells the rep. A sequencing node can follow. |

## How product events reach Default

Default has no native integration with product analytics tools or warehouses, and it does not pull
events, users, or cohorts from them. The tool, or a sync you run, sends each event to Default.

| Source | Trigger | Setup |
| - | - | - |
| Segment, PostHog, Mixpanel, or Amplitude | **Incoming Webhook** | The tool sends each event as a JSON `POST` with your webhook secret. See [Product analytics](/settings/integrations/product-analytics). |
| A warehouse or reverse ETL (extract, transform, load) tool, such as Hightouch or Census | **Incoming Webhook** | Each synced row is 1 request. See [Warehouses and reverse ETL](/settings/integrations/warehouses). |
| Your own backend, or a sync that calls the Default API | **API** | Each call sends the user's email and the fields of the trigger's **Connected Form**. See [Route and book through the API](/api/route-and-book). |
| A sync that writes usage to Salesforce or HubSpot | **CRM Record Updated** | Add a **Condition** with **Fields Updated** **contains any of** the usage field. See [Triggers](/workflows/triggers#crm-record-updated). |

The rest of this guide uses the **Incoming Webhook** trigger. With the **API** trigger, the email in
each call already identifies the person, so skip the identity mapping step.

## Setup for PQL routing

| Requirement | Where | Why |
| - | - | - |
| Salesforce or HubSpot connected | **Settings** → **Integrations**. See [Salesforce](/settings/integrations/salesforce) or [HubSpot](/settings/integrations/hubspot). | **Match Record**, **Create Record**, and **Update Record** read and write the CRM. |
| Enrichment providers or a waterfall | **Settings** → **Integrations** → **Enrichment**, and [Waterfalls](/settings/configurations/waterfalls) | **Enrich Data** uses them. You don't need an account or API key with the provider. Enrichment uses credits. |
| A queue for PQL reps with a routing policy | **Routing** → **Queues** and **Policies**. See [Queues](/queues) and [Policies](/policies). | **Round-Robin** picks the rep. Include **Objects** in the policy's **Assignment Units**. |
| Each rep mapped to their CRM user and Slack user | **Settings** → **Users**. See [Users](/settings/users). | Default writes the rep as the CRM owner and sends the Slack message through these mappings. |
| Slack connected | **Settings** → **Integrations** → **Slack**. See [Slack](/settings/integrations/slack). | **Send Slack Message** posts through it. |
| A CRM field for the product signal (optional) | Salesforce or HubSpot | Default writes to fields that already exist in your CRM, for example a `Product Signal` text field. |

## Send events Default can qualify on

The captured sample defines the fields that later nodes can pick, and their types. Before you
capture it:

* Send 1 event per request. A body that holds an array of events starts 1 run with the whole array.
* Send running totals. Each request starts its own run, and the run sees only that request's body.
  Default does not add up events across requests, so send the total, such as `seats_used`, or let
  the analytics tool or warehouse decide when the threshold is reached.
* Send numbers as JSON numbers. A sample value of `6` lets a condition use comparisons such as
  is at least. A value of `"6"` is text and offers only text comparisons.
* Fill every field in the sample. A key missing from the sample does not appear in the data picker,
  and a `null` value gives the field no type.
* Name the email field `email`, or end its name in `_email`. Conditions then offer
  **is work domain**, and the value picker offers **Domain from email**.

```json theme={null}
{
  "event": "Usage Threshold Reached",
  "email": "jane@acme.com",
  "first_name": "Jane",
  "last_name": "Doe",
  "account_domain": "acme.com",
  "plan": "Free",
  "seats_used": 6,
  "projects_created": 14
}
```

## Build a PQL routing workflow for Salesforce

This example routes free-plan users with at least 5 seats at companies with at least 50 employees.
People already in Salesforce keep their owner. New PQLs at an existing Account go to the account
owner, and the rest go to a queue named `PQL Reps`.

<Steps>
  <Step title="Add the Incoming Webhook trigger">
    In **Workflows**, create a workflow and add the **Incoming Webhook** trigger. Under
    **Connected Webhook**, select **Create new webhook**. Copy the **Ingestion URL** and the
    **Bearer secret** into your analytics tool or sync, and send the `Authorization: Bearer <secret>`
    header with each request.
  </Step>

  <Step title="Capture a sample and map the person">
    Select **Listen for test event** and send 1 event, or select **Paste a sample**, paste the
    example above, and select **Save sample**. Under **Identity mapping**, set **Person email** to
    `email`. Later nodes, including **Enrich Data** and **Round-Robin**, then know who the event is
    about.
  </Step>

  <Step title="Check the usage signal first">
    Add **Multi-Branch** with a branch named `Product signal`. Add the conditions from the event,
    found in the data picker under **Workflow** → **Trigger** → **Incoming webhook**: `plan` equals
    `Free`, `seats_used` is at least `5`, and `email` **is work domain**. Leave **Else** empty, so
    other events end the run before they use enrichment credits.
  </Step>

  <Step title="Check Salesforce for the person">
    On `Product signal`, add **Match Record**. Set **CRM** to **Salesforce** and **Record** to
    **Lead**, and under **Match Details**, match the Lead's **Email** to `email` from the trigger.
  </Step>

  <Step title="Update existing Leads and tell their owner">
    On the **Match** branch, add **Update Record** with **Platform** set to **Salesforce** and
    **Record Type** set to **Lead**. Set **Record to update** to the Lead's ID from the
    **Match Record** step, and under **Fields**, set your `Product Signal` field to `event` from the
    trigger. Then add **Send Slack Message** and set **Recipients** to the Lead's **Owner ID** from
    the **Match Record** step. The owner stays the same.
  </Step>

  <Step title="Enrich new people">
    On the **No Match** branch, add **Enrich Data**. Set **Enrichment target** to **Both** and
    **Enrichment source** to your waterfall. Leave **Email / Domain (optional)** empty, and Default
    uses the mapped **Person email**.
  </Step>

  <Step title="Qualify on fit">
    Add a second **Multi-Branch** with a branch named `PQL`, for example where the enriched number of
    employees is at least `50` and the country is any of the markets you sell to. Leave **Else**
    empty, or see [Keep every signup in the CRM](#keep-every-signup-in-the-crm).
  </Step>

  <Step title="Look for the company's Account">
    On `PQL`, add **Match Record** with **CRM** set to **Salesforce** and **Record** set to
    **Account**. Under **Match Details**, choose **Website** and **domain matches**, and for the value
    pick `email` from the trigger and choose **Domain from email**. A domain field from the event,
    such as `account_domain`, works too. See
    [Match on the domain](/lead-routing/matching/accounts-and-companies#match-on-the-domain).
  </Step>

  <Step title="Give PQLs at known Accounts to the account owner">
    On the **Match** branch, add **Create Record** with **Platform** set to **Salesforce** and
    **Record Type** set to **Lead**. In **Mapped Fields**, map **Email**, **First Name**,
    **Last Name**, and **Company** from the event or the enrichment, set `Lead Source` to `Product`,
    and set **Owner ID** to the Account's **Owner ID** from the **Match Record** step. Add
    **Send Slack Message** with **Recipients** set to the same **Owner ID**.
  </Step>

  <Step title="Round-robin the rest">
    On the **No Match** branch, add **Round-Robin** and set **Queue** to `PQL Reps`. Add
    **Create Record** with the same **Mapped Fields**, and set **Owner ID** to
    **Latest assigned user** under **Round robin**. Add **Send Slack Message** with **Recipients** set
    to **Latest assigned user**. In **Message**, type `{{` to insert `event`, `seats_used`, and
    `plan`, so the rep sees why the user qualified.
  </Step>

  <Step title="Test and publish">
    Select **Test**, edit the sample to use your own email, and select **Run Test**. Check the Lead
    and its owner in Salesforce and the Slack message. 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 test performs real actions. It creates the Lead, counts the routing assignment, and sends the
  Slack message. Use your own email and a test queue.
</Warning>

To start a sequence for the rep's new PQLs, add a sequencing node after **Create Record** on the
round-robin branch, with the sender set to **Latest assigned user**. See
[Send routed leads to sales sequences](/guides/routed-leads-to-sequences).

### Keep every signup in the CRM

To record users who do not qualify yet, add **Create Record** on the fit check's **Else** branch with
the same **Mapped Fields**, without setting **Owner ID**. When the same user sends a qualifying event
later, **Match Record** finds the Lead and takes the **Match** branch, which keeps the owner. To route
those Leads too, add a **Multi-Branch** on the **Match** branch whose condition matches only Leads
your team treats as unassigned, then **Round-Robin** and **Update Record**.

## Qualify with AI Prompt instead of fixed rules

When the signal is in text, such as a signup answer about the use case or a job title, add
**AI Prompt** after **Enrich Data** in place of the fit check. In **Dynamic prompt**, type `{{` to
insert the event fields and the enriched data, list the labels the model may return, for example
`PQL` and `Not yet`, and ask for the label only. Then add **Multi-Branch** with a branch where the
AI Prompt **Output** equals `PQL`. **Else** catches every other reply and failed requests. See
[Classify free-text answers](/lead-routing/qualification#classify-free-text-answers-with-ai-prompt)
and [Score with AI Prompt](/lead-routing/lead-scoring#score-with-ai-prompt).

## Salesforce and HubSpot differences for PQLs

| | Salesforce | HubSpot |
| - | - | - |
| Person record | **Lead**. Existing customers' users are often Contacts, so add a second **Match Record** on **Contact** on the Lead's **No Match** branch. | **Contact** |
| Company record and domain field | **Account**, matched on **Website** | **Company**, matched on its Company Domain Name property |
| Owner field to write | **Owner ID** | **Contact owner** |
| Account owner to copy | The Account's **Owner ID** | The Company's Company owner property |
| Required on create | **Last Name** and **Company** on every Lead | Map **Email** so the next event finds the contact |
| Duplicate protection | A Salesforce duplicate rule that blocks a new Lead with an existing email | HubSpot keeps a contact's email unique and rejects the second create |

See [Salesforce lead routing](/lead-routing/salesforce) and [HubSpot lead routing](/lead-routing/hubspot)
for the owner write-back in each CRM.

## Check that PQL routing worked

* **Runs**: open the workflow, select **Runs** in the top bar, and open a run. Each **Multi-Branch**
  step lists every condition with the value it found and **TRUE** or **FALSE**, and the
  **Round-Robin** step shows the rep it picked. See [Run logs](/workflows/run-logs).
* **Assignments**: in **Routing**, select **Assignments**, then **Object assignments**, to see each
  round-robin PQL. An owner copied from the Account does not count as a queue assignment.
* The CRM record: the new Lead or contact has the rep as owner and `Product` as its source.

## What Default does not do for PQLs

* Connect natively to Segment, PostHog, Mixpanel, Amplitude, Hightouch, or Census, or show a card for
  them on the **Integrations** page.
* Pull events, users, or cohorts from these tools, or query a warehouse on a schedule.
* Accept webhook requests without an `Authorization: Bearer` header. If a tool cannot set that
  header, put a service that can between the tool and Default.
* Split a batch of events into separate runs, or add up events across requests.

## Troubleshoot PQL routing

| What you see | What to check |
| - | - |
| The workflow never starts | A `401` response means a missing or wrong secret, and a `410` means the webhook's **Active** switch is off. Default answers `200` even when no live workflow uses the webhook, so check that the status reads **Live**. |
| A usage field is missing from the data picker, or offers only text comparisons | Capture a new sample in which every field has a value and numbers are sent as JSON numbers. |
| **Enrich Data** finds no person | Set **Person email** under **Identity mapping**, or pick the email in **Email / Domain (optional)**. |
| **Domain from email** or **is work domain** is not offered for the email | Rename the payload field to `email` or a name ending in `_email`, or use a domain field from the event. |
| **Create Record** fails on a missing field | Map **Last Name** and **Company** for a Salesforce Lead, from the event or from **Enrich Data**. |
| The owner gets a Slack message for every event | Each request starts a run. Send the threshold event once from the analytics tool, or remove **Send Slack Message** from the **Match** branch. |

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.