How to find bot traffic in your Segment data

Bots that run your analytics.js snippet become Segment MTUs, and you pay for them. This guide gives seven signals that find them in your warehouse, with SQL for each one. All the signals use only the data that analytics.js collects automatically.

Updated October 19, 2026

Do you want the result without the SQL? The free segment_bot_audit dbt package does all the steps in this guide and prints the bot share of your MTUs and its cost.

Where to look

Segment writes each source to its own schema in your warehouse. The pages table is the most useful, because the default analytics.js snippet makes a page call on each page load. These columns are in each row:

ColumnWhat it contains
anonymous_idThe ID that analytics.js gives to each browser. Each new ID that has no user_id is an MTU.
context_user_agentThe user agent of the browser
context_ipThe IP address, which Segment adds on its servers
pathThe page path
timestampThe time of the event

You do not need identify or track calls. If your team uses them, a user_id is a good sign of a real person. The audit never counts a visitor with a user_id as a bot.

The seven signals

SignalStrengthWhat it finds
Bot user agentStrongA known crawler, monitor, AI agent or headless browser in the user agent
Malformed user agentStrongA page call with a user agent that is too short or does not start with Mozilla/
Scanner pathStrongPage calls on paths such as /wp-login.php or /.env
Fast pagesStrongMany page calls less than one second apart
Regular intervalStrongPage calls at an almost fixed interval
Datacenter IPWeakAn IP address in a cloud provider range
IP and user-agent burstWeakMany new IDs from one IP address and user agent in one hour

One strong signal is enough. Weak signals also match real people, such as an office behind one IP address or a person on a cloud VPN. For this reason, count a weak signal only when the other weak signal is also present.

The SQL below uses like where possible, so it works on most warehouses. Replace segment.pages with your schema and table.

1. Bot user agent

Many bots tell you what they are. Look for crawler, monitor and automation names in the user agent. Look out for real phones with "bot" in the model name, such as the Cubot brand.

select anonymous_id, context_user_agent
from segment.pages
where (
    lower(context_user_agent) like '%bot%'
    or lower(context_user_agent) like '%crawl%'
    or lower(context_user_agent) like '%spider%'
    or lower(context_user_agent) like '%headless%'
    or lower(context_user_agent) like '%puppeteer%'
    or lower(context_user_agent) like '%playwright%'
    or lower(context_user_agent) like '%lighthouse%'
    or lower(context_user_agent) like '%pingdom%'
  )
  and lower(context_user_agent) not like '%cubot%'

2. Malformed user agent

Each modern browser sends a long user agent that starts with Mozilla/5.0. A page call with a short user agent, or a user agent without Mozilla/, comes from a script. Use this signal only on the pages table. Mobile and server-side libraries send other user agents, or none.

select anonymous_id, context_user_agent
from segment.pages
where context_user_agent is not null
  and (length(context_user_agent) < 30
       or lower(context_user_agent) not like 'mozilla/%')

3. Scanner path

Vulnerability scanners request paths that do not exist on your site. If your 404 page has the Segment snippet, each request is a page call. Change the list if your site really uses one of these paths.

select anonymous_id, path
from segment.pages
where lower(path) like '%wp-login%'
   or lower(path) like '%wp-admin%'
   or lower(path) like '%xmlrpc%'
   or lower(path) like '%phpmyadmin%'
   or lower(path) like '%/.env%'
   or lower(path) like '%/.git%'

4. Fast pages

A person needs some seconds to read a page. A script opens many pages in less than one second. Count the gaps between page calls that are shorter than one second. The date functions are different on each warehouse. This example is for Snowflake.

with gaps as (
    select
        anonymous_id,
        datediff(
            'millisecond',
            lag(timestamp) over (partition by anonymous_id order by timestamp),
            timestamp
        ) as gap_ms
    from segment.pages
)
select anonymous_id, count(*) as fast_gaps
from gaps
where gap_ms < 1000
group by anonymous_id
having count(*) >= 5

5. Regular interval

A person reads pages at an irregular speed. A scheduled script opens pages at an almost fixed interval. Compare the standard deviation of the gaps with the average gap. A value below 0.05 is a strong sign of a script.

-- Use the gaps CTE from signal 4.
select anonymous_id, avg(gap_ms) as avg_gap_ms
from gaps
where gap_ms is not null
group by anonymous_id
having count(*) >= 10
   and avg(gap_ms) between 1000 and 600000
   and stddev_samp(gap_ms) / avg(gap_ms) <= 0.05

6. Datacenter IP (weak)

Most bots run in the cloud. AWS, Google Cloud, DigitalOcean, Oracle and Linode publish their IP ranges. Load the ranges into a table, and join each IPv4 address to them. People on a VPN or a corporate proxy also use cloud IP addresses, so this signal is weak alone.

The audit package includes the ranges as a seed file, with a script to update them. The IP functions are different on each warehouse, so the package converts each address to an integer for the comparison.

7. IP and user-agent burst (weak)

A bot with no saved data gets a new anonymous_id on each visit. Thus, one machine makes many IDs with the same IP address and user agent. An office with one IP address can do this too, so this signal is also weak alone.

select context_ip, context_user_agent,
       date_trunc('hour', timestamp) as event_hour,
       count(distinct anonymous_id) as ids
from segment.pages
where context_ip is not null
group by 1, 2, 3
having count(distinct anonymous_id) >= 10

Combine the signals and count the cost

  1. For each anonymous_id in each month, calculate each signal.
  2. Mark the ID as non-human if it has one strong signal, or both weak signals.
  3. Do not mark an ID that has a user_id in the month.
  4. Count the non-human IDs for each month, and multiply by your price for each MTU.

The segment_bot_audit package does these steps for you. It also handles the tables and columns that some setups do not have, such as a missing identifies table.

dbt deps
dbt build --select package:segment_bot_audit
dbt run-operation segment_bot_audit.print_report

How to stop bots from counting as MTUs

Segment counts an MTU when an event reaches Segment. Destination filters act after that point, so they do not reduce MTUs. To stop the MTU, do not load analytics.js for bots.

In the standard snippet, remove the analytics.load and analytics.page calls from the end of the snippet. Then add this code after the snippet:

<script>
  var botPattern = /bot|crawl|spider|slurp|headless|phantomjs|puppeteer|playwright|selenium|lighthouse|pingdom|uptime/i;
  var isBot = navigator.webdriver === true || botPattern.test(navigator.userAgent);
  if (!isBot) {
    analytics.load("YOUR_WRITE_KEY");
    analytics.page();
  }
</script>

If you use the npm package, make the same check before you load it:

import { AnalyticsBrowser } from '@segment/analytics-next';

const botPattern = /bot|crawl|spider|slurp|headless|phantomjs|puppeteer|playwright|selenium|lighthouse|pingdom|uptime/i;
const isBot = navigator.webdriver === true || botPattern.test(navigator.userAgent);

export const analytics = isBot ? null : AnalyticsBrowser.load({ writeKey: 'YOUR_WRITE_KEY' });

Do a test before you deploy. A wrong pattern can block real visitors. Measure first with the audit, and then block only the patterns that you see in your own data.

This check stops the bots that identify themselves. Bots that copy a real browser need the server-side signals in this guide, such as the IP address and the timing.

Questions

Can I see bot traffic in the Segment warehouse tables?

Yes, for most bots. Each page call from analytics.js has the user agent, the IP address, the page path and a timestamp. These columns are enough to find crawlers, headless browsers, scanners and scripts.

Do I need identify calls to find bots?

No. The signals in this guide use only the data that analytics.js collects automatically on each page call. A visitor with a userId is usually a real person, but the signals do not need one.

Does Segment filter bots for me?

No. Segment has no built-in bot filter. Destination filters remove bots from some tools, but the bots still count as MTUs in Segment.

How do I exclude bots from Segment?

Do not load analytics.js for bots. Do a check of the user agent and navigator.webdriver before you call analytics.load. This stops the event before Segment counts an MTU.

Read next: Segment MTU pricing: how much of your bill is bots?