Internal traffic
The developer machine you test from, the internal tools that hammer your API, the tab you leave open on your own product all day — that's real traffic, but it isn't your customers. Internal traffic filtering keeps an account's own activity out of its dashboards, the way GA4's "internal traffic" does. The fast path is a single click: Crossdeck detects the network your dashboard request came from and excludes it — no IDs to copy, nothing to look up. And it does this with one important difference from GA4: Crossdeck never drops the events. They are always collected and stored. The exclusion is a view applied at read time, on every surface, and it reverses with a single toggle.
TL;DR
- Internal events are always collected and stored. Filtering is a query-time view, never an ingestion change. A misconfigured rule cannot lose data — the full firehose stays auditable.
- Every surface hides internal traffic by default — Overview, People, Activity, Revenue, Funnels, and Cohorts.
- One click excludes your own network. Settings → Internal traffic → Exclude my network detects the IP your dashboard is browsing from and adds it for you — the GA-style "exclude my traffic," no IDs to copy. You can also add IP / CIDR ranges by hand, or tag a single browser with
?crossdeck_internal=1. - IPv6 is excluded by network, not by exact address. Privacy addressing rotates your IPv6 host suffix, so Crossdeck excludes the stable
/64prefix; on IPv4 it excludes the exact shared address. Either way the rule keeps matching you tomorrow. - Reclassification is retroactive. Add or remove a range — or sign in as a previously-flagged user — and matching events reclassify on the next read, because the raw signals are persisted on every event and re-derived, not stamped once at ingest.
- One toggle flips it back. "Show internal traffic" is off by default; turn it on to see the full stream while you debug.
What counts as internal
Every event Crossdeck stores carries an actor_type — a small enum describing who generated it:
| Value | Meaning |
|---|---|
customer | A real person using your app. The default — every event is a customer event until a rule says otherwise. |
internal | Activity you generate yourself: a developer machine, an internal tool, you browsing your own product. Excluded from dashboards by default. |
test | Reserved. A future slot for sandbox / synthetic actors. Not wired up yet — present so the model doesn't need a migration later. |
"Internal" is the only classification active today. It answers a narrow, practical question: is this event me, or is it a customer? Getting that line right is what makes "active users" mean active customers, and what stops your own paywall-testing from showing up as a conversion.
Collected always, filtered on read
This is the load-bearing principle, and it is worth stating plainly: Crossdeck never drops an internal event at ingestion. Internal traffic is collected, validated, and stored exactly like customer traffic. Classification — and exclusion — happen later, at query time, every time a dashboard reads.
The alternative — dropping events that match an internal rule at the door — is how most "internal traffic" features work, and it is a trap. A fat-fingered CIDR range, an IP that turns out to be a shared corporate gateway, a rule a teammate adds without thinking it through: any of these can silently delete real signal you will never get back. There is no undo on data you didn't keep.
Because rules change and identity resolves over time. An event you couldn't classify at ingest (anonymous, no resolved user yet) might become classifiable an hour later when the session stitches to a flagged account. A query-time model re-asks the question on every read against the current rules; an ingestion-time model freezes a verdict the instant the event arrives and can never revisit it. Keeping the raw firehose intact is what makes the retroactive case below possible at all.
Excluding your own traffic
You manage this in Settings → Internal traffic. The primary control is a single button; two more signals cover the cases it can't reach. You can use any combination of them.
1 · One click — "Exclude my network"
Click Exclude my network and Crossdeck reads the public IP your dashboard request came from — derived exactly the way the SDK stamps source_ip on your visitors' events — and saves it as an exclusion. No IDs to copy, nothing to look up. This is GA4's "exclude my traffic" button, and for most accounts it is the only step you need: a developer browsing their own product is anonymous traffic, and its source IP is the one signal that visit shares with you.
If you're on IPv6, your device's address suffix rotates for privacy (RFC 4941) — excluding today's exact /128 would stop matching you tomorrow. So Crossdeck excludes the stable /64 network prefix instead. On IPv4, where a home or office shares one public address behind NAT, it excludes that exact host. Either way the saved rule keeps matching you across sessions — which is the whole point.
2 · IP addresses and CIDR ranges, by hand
Below the button you can add individual IP addresses or CIDR ranges yourself — your office or VPN egress, a QA device's network, an internal dashboard that hammers your API. Any event whose source IP falls inside a listed range is internal. Each saved range shows as a chip you can remove with one click.
A residential or coffee-shop IP is shared and reassigned; a corporate gateway can sit in front of real customers. The never-drop design is your safety net: if a range turns out to be too broad, remove the chip and the affected events reclassify back to customer on the next read. Nothing was lost.
3 · The browser opt-out flag
Visit any tracked surface with ?crossdeck_internal=1 in the URL. The web SDK persists a flag in that browser's localStorage, and every later event from that browser is tagged internal — until you clear it by visiting with ?crossdeck_internal=0.
This covers the cases IP and identity miss: a dynamic home IP that changes nightly, or you browsing your own product logged out. It is per-browser and self-service — no Settings change required to set or clear it.
Keep https://yourapp.com/?crossdeck_internal=1 and …=0 as two bookmarks. One click tags the browser before a testing session; one click clears it after. Because the flag lives in localStorage, it survives reloads and new tabs on the same browser profile, but not a different browser, a private window, or a cleared cache.
What about flagging a specific user?
Earlier versions let you mark an individual Crossdeck User as internal in Settings. We retired that field in favour of the one-click model above — for the developer excluding their own testing, IP is simpler and doesn't depend on being signed in (your self-traffic is usually anonymous, which an ID rule can't match). The resolved-identity signal itself didn't go away: any user you flagged previously is still honoured at read time, and it remains the authoritative reason when an identity is known. Per-user internal flagging returns as a managed feature alongside Teams.
How a signal wins
An event is internal if any of these signals matches — you don't have to pick one:
- IP / CIDR — the event's source IP falls in a range you added (via the button or by hand). The primary signal for excluding your own traffic.
- Resolved identity — the event resolves to a Crossdeck User that was flagged internal. Honoured for any user flagged previously, and authoritative whenever an identity is known.
- Opt-out flag — the browser carries the persisted
?crossdeck_internal=1flag.
The verdict doesn't depend on the order — a single match makes the event internal. A customer event stays customer only when none of these match.
Retroactive reclassification
Identity resolves over time. A visitor lands anonymously, clicks around, and only signs in three pages later — at which point identity stitching connects the earlier anonymous events to the now-known user. If that user is flagged internal, those earlier events must become internal too, even though they were unclassifiable when they arrived.
Crossdeck handles this by persisting the raw signals on every event — the resolved user ID (if known at the time), the source IP, and the browser opt-out flag — rather than a single classification stamped once at ingest. Because the inputs are kept, actor_type can always be re-derived from the current rules:
- An identity-merge re-derives
actor_typefor the stitched session, so an anonymous event resolved to a flagged user flips to internal retroactively. - A rule change — flagging a user, adding or removing a CIDR — takes effect on the next read for all matching events, backwards and forwards.
A value stamped at ingest ("internal: true") is frozen — it can't learn that the session later belonged to you. Storing the inputs and re-deriving on demand is what lets a week-old anonymous event become correctly internal the moment its identity is known. It is the same instinct behind the never-drop rule: keep the evidence, re-decide on read.
Showing internal traffic
Every dashboard carries a "Show internal traffic" toggle. It is off by default — your own activity is hidden on Overview, People, Activity, Revenue, Funnels, and Cohorts without any setup. Turn it on to fold internal events back into the view while you are debugging or developing.
It is purely a view preference. Turning it on does not change what was collected, and turning it off does not delete anything — both states read the same stored firehose and differ only in the filter applied. There is no ingestion-time version of this switch, by design.
What gets stored
For the curious, the shape underneath:
- Hot path. A best-effort
actor_typeis stamped at ingestion so the dashboard's fast, live reads can filter immediately — alongside the raw signals (resolved user ID if known, source IP, opt-out flag) needed to re-derive it. - Identity merges re-derive
actor_typefor the affected session, so stitched sessions reclassify without waiting for a batch job. - Cold path. The analytics warehouse re-derives
actor_typefrom the stored raw signals and the current rules at batch time, so historical queries reflect the latest configuration — not whatever was true the moment an event arrived.
The two paths agree because they answer the same question from the same stored inputs; the hot path is just an optimisation so live dashboards don't wait for the warehouse.
Bot traffic
Internal traffic is you; bot traffic is no one. Crawlers, scrapers, headless browsers, and uptime monitors generate real requests that aren't humans at all, and Crossdeck classifies them separately. Every event carries an is_bot flag, and every dashboard query filters bots by default — a "show bots" toggle on the Activity page folds them back in when you want them (knowing Googlebot crawls you is useful for SEO debugging).
Classification runs at ingest, on the request's headers, checking three signals in order — the first one present wins:
- Cloudflare verified bot. Cloudflare marks known good bots — Googlebot, Bingbot, Applebot, social-preview fetchers — by reverse-DNS-checking the caller's IP against the bot's published ranges. If the request carries Cloudflare's
cf-verified-botheader, it's a bot. Highest confidence: this is verified identity, not a guess. - Cloudflare bot score. Cloudflare's ML assigns requests a 1–99 score, lower meaning more bot-like. A score below 30 — Cloudflare's own threshold — classifies as bot; this catches scrapers, headless browsers, and AI crawlers that aren't on the verified list. A score of 30 or above is trusted as human, and no further check runs.
- User-Agent patterns. When neither Cloudflare header is present, a small curated list (~50 patterns) covers the major crawlers, automation frameworks, and monitoring services. The list is deliberately short: a misclassified human silently disappears from your analytics, while a missed bot only inflates a count slightly — so false positives are the failure mode it's tuned against. An empty User-Agent is never penalised, because some legitimate clients (older mobile browsers, embedded webviews) ship without one.
One difference from internal traffic is worth being explicit about: the bot verdict is stamped once, at ingest — on every event in that request — rather than re-derived at read time. Future requests from the same visitor are re-classified independently, so a scraper that rotates User-Agents is caught session by session, not retroactively across its history. Like internal traffic, though, bot events are never dropped: they're stored with is_bot set, and the exclusion is a query-time filter you can lift with the toggle.
Related
- Identify users — how anonymous sessions resolve to a Crossdeck User ID, the mechanism that makes the user flag and retroactive reclassification work.
- Identity verification — how Crossdeck stitches identities across devices and sessions.
- Sandbox vs production — the other axis your dashboards filter on. Internal traffic is orthogonal: an event is sandbox-or-production and customer-or-internal.