---
ref: workflow:visitor-company-fit
title: Alert sales when an ideal-fit company browses your site
author: thedogwiththedataonit
tools: [tool:posthog/run-sql-query, tool:people-data-labs/identify-ip-company, tool:typesafe/answer-typed-questions, tool:hubspot/search-crm-records, tool:slack/post-message]
tags: [capability:analyze-product-usage, capability:classify-signals, capability:manage-crm, capability:route-alerts, capability:track-intent, channel:chat, channel:website, has:api, motion:inbound]
updated: 2026-09-29
---

# Alert sales when an ideal-fit company browses your site

Resolves anonymous PostHog visitors to companies with People Data Labs, judges fit and buying stage with Jev, and alerts sales in Slack.

Set up the tools below, then run the steps in order for the user, carrying each step's results into the next. The run is done when the user has the outcome below.

## Outcome

- A table of the companies behind your anonymous visitors, with the pages each viewed, its fit level, buying stage and Jev's confidence.
- A Slack post for each company that fits and is evaluating or ready to talk, with its HubSpot owner.

## Inputs

Ask the user for these before you start.

- `lookback_days`: the window to read visits from, e.g. 1
- `min_pages`: how many pages one IP address must view to count, e.g. 3
- `max_lookups`: the most IP addresses to resolve in one run, since each match is charged, e.g. 100
- `min_match`: the lowest People Data Labs match confidence to keep, e.g. high
- `ideal_customer`: the companies you sell to, in a sentence or two, e.g. B2B software companies with 50 to 1,000 employees
- `min_fit`: the lowest fit level worth an alert, e.g. good
- `min_confidence`: how sure Jev must be before its answer is used without you, e.g. 0.8
- `alerts_channel`: the Slack channel to post in, e.g. #sales-signals

## Set up

### Run a SQL query (PostHog, tool:posthog/run-sql-query)

Use the first option your agent supports.

Note: Needs a personal API key with the `query:read` scope. Returns up to 100 rows by default and up to 50,000 with an explicit `LIMIT`; it is for ad-hoc analysis, not bulk export.

#### MCP (official, remote)

Add this server to your agent's MCP settings, then sign in when asked.

```json
{ "mcpServers": { "posthog": { "url": "https://mcp.posthog.com/mcp" } } }
```

Call the MCP tool `execute-sql`.

#### CLI (official)

Install the command, then confirm it runs.

```sh
npm install -g @posthog/cli@latest
posthog-cli --version
```

Run `posthog-cli api call execute-sql`.

Set `$POSTHOG_CLI_API_KEY` in your environment first (get a key: https://app.posthog.com/settings/user-api-keys?preset=mcp_server).

### Identify the company behind an IP address (People Data Labs, tool:people-data-labs/identify-ip-company)

Use the API.

- Base URL: https://api.peopledatalabs.com
- Endpoint: `GET /v5/ip/enrich`
- Auth: send the header `X-Api-Key: $PDL_API_KEY`
- Get a key: https://dashboard.peopledatalabs.com/api-keys

Note: Charged per match.

### Answer typed questions (TypeSafe AI, tool:typesafe/answer-typed-questions)

Use the API.

- Base URL: https://api.typesafe.ai
- Endpoint: `POST /v1/systemone`
- Auth: send the header `Authorization: Bearer $TYPESAFE_API_KEY`
- Get a key: https://console.typesafe.ai

Note: Put every question about one record in one request. `GET /v1/models` lists the models and is the cheapest check of a key; a request over the rate limit gets a 429 with a Retry-After header.

Note: Send `state`, `model` (`jev-latest`) and named `questions`, each with `type`, `instructions` and `criteria`: a choice maps labels to descriptions, a score lists its levels in order, a noul's is optional. Read a score by its most likely level; send unsure answers to a person.

### Search CRM records (HubSpot, tool:hubspot/search-crm-records)

Use the first option your agent supports.

Note: The MCP tool takes up to five groups of six filters and returns up to 200 records per page.

#### MCP (official, remote)

Add this server to your agent's MCP settings, then sign in when asked.

```json
{ "mcpServers": { "hubspot": { "url": "https://mcp.hubspot.com" } } }
```

Call the MCP tool `search_crm_objects`.

#### API (official)

- Base URL: https://api.hubapi.com
- Endpoint: `POST /crm/objects/2026-09/{objectType}/search`
- Auth: send the header `Authorization: Bearer $HUBSPOT_API_KEY`

Note: Use a service key or an app's static access token with the CRM scopes the calls need; paths carry a dated version such as `2026-09`.

### Post a message (Slack, tool:slack/post-message)

Use the first option your agent supports.

Note: Needs the `chat:write` scope. Over MCP it posts as the signed-in user; the API and CLI post as the app's bot.

#### MCP (official, remote)

Add this server to your agent's MCP settings, then sign in when asked.

```json
{ "mcpServers": { "slack": { "url": "https://mcp.slack.com/mcp" } } }
```

Call the MCP tool `slack_send_message`.

#### CLI (official)

Install the command, then confirm it runs.

```sh
curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash
slack --version
```

Run `slack api chat.postMessage`.

Set `$SLACK_BOT_TOKEN` in your environment first (get a key: https://api.slack.com/apps).

Before step 1, confirm access to each service with its cheapest read-only call, like a list or a search. Never send or change anything to test access.

## Steps

1. **Find engaged visitors** with Run a SQL query (PostHog). Select `$pageview` events from the last `lookback_days` by persons with no `email`, grouped by the event's `$ip`, with the distinct `$pathname` values and the view count. Keep the IP addresses with at least `min_pages` pages, with their pages.
2. **Resolve the companies** with Identify the company behind an IP address (People Data Labs). Look up each IP address, up to `max_lookups`, requesting the IP's metadata. Skip VPN, proxy, mobile and hosting addresses and matches below `min_match`. Keep each company's name, website, size and industry, with the pages its visitors viewed.
3. **Judge fit and stage** with Answer typed questions (TypeSafe AI). Send each company's facts and pages as the state, with a score `fit` whose levels describe the match with `ideal_customer`: no fit (matches none of it), weak (matches some), good (matches most) and ideal (matches all); and a choice `stage` of researching (blog and docs), evaluating (pricing, comparisons, case studies or security), ready_to_talk (contact or demo pages), existing_customer (the app, login or billing) and job_seeker (careers). Keep each answer with its confidence.
4. **Check the unsure ones with the user** yourself. Show the user the companies whose fit or stage confidence is below `min_confidence`, and keep what the user decides. Keep the companies with a fit of `min_fit` or better that are evaluating or ready to talk.
5. **Find the owner** with Search CRM records (HubSpot). Search companies by each website's domain. Keep each company's record ID and `hubspot_owner_id`, and look up each owner's name, a read-only call. Note the ones with no record.
6. **Alert** with Post a message (Slack). Post each kept company to `alerts_channel` with its size, industry, fit level, stage, the pages viewed and its owner's name.

## Notes

Visitor IP addresses are personal data in many places, so run this only if your privacy notice covers sharing them with an enrichment vendor. PostHog keeps the `$ip` property only when the project does not discard client IP data; check the project settings first. The stage comes from which pages were viewed, which is why the pages go into the state alongside the company.

It finds companies, not people: reach the right person through the account owner, never by guessing who visited. Run it daily with `lookback_days` set to 1.

## Rules

- Use only the services set up above. The read-only calls they need, like listing ids or polling for results, are fine.
- Ask the user before anything that sends messages, costs money, or changes data, and say how many records it touches. One approval covers a batch the user has seen.
- Never print API keys.
