How Traffic Routing Works in Campaigns
What is routing and why is it needed
Routing is a mechanism that determines what to show to each specific user who clicks on a tracking link.
Business tasks of routing:
Geo-distribution: if you are pouring from FB to EU and LATAM — need to send European traffic to the offer 1win, and LATAM — to mostbet
A/B testing: two creatives or two PWA designs — need a 50/50 split and Sticky Flow so that one user does not see both options
Protection from moderation: FB/Instagram moderators click on ads — they need to be sent to a harmless White Page
Device optimization: Android → PWA with installation, iOS → separate funnel (or external PWA / iOS App)
Bot filtering: cut off bots and proxies before passing click_id to the partner
Example: You launch a campaign from FB. You created two Stream Sets: the first one — for mobile Android traffic from target GEO (PWA + offer 1win), the second — for desktop (landing + offer parimatch). Everything else (bots, moderators, non-target countries) goes to the White Page.
Campaign Architecture
A campaign in Adset.Pro consists of several levels:
Campaign (Campaign)
├─ Tracking link: https://domain.com/track/{campaignId}
├─ Sticky Flow (on/off)
├─ White Page (none / url / system)
│
└─ Stream Set #1 (filter set + flows)
│ ├─ Filters (applyMode: AND / OR)
│ │ ├─ user_device = mobile
│ │ ├─ user_os = Android
│ │ └─ user_country IN [UA, KZ, UZ]
│ ├─ Pixels (FB CAPI, TikTok)
│ └─ Flows (Flows):
│ ├─ Flow 1 (weight 70) → PWA-1 → CPA-1 → Offer-A
│ └─ Flow 2 (weight 30) → PWA-2 → CPA-2 → Offer-B
│
└─ Stream Set #2 (other filters)
├─ Filters: user_device = desktop
└─ Flows:
└─ Flow 1 (weight 100) → Landing → CPA-3 → Offer-C
The tracking link of the campaign is a single URL for the traffic source. The tracker itself decides which flow to direct each click.
Stream Sets: rule sets
Stream Set is a set of filters + a list of flows. A campaign can contain multiple Stream Sets.
When a click comes in, the tracker checks the Stream Sets top to bottom (by priority) and applies the first suitable one. If no Stream Set fits — the click goes to the White Page.
Filter operation modes
Mode | Logic | When to use |
|---|---|---|
AND | All conditions must match | Exact targeting (country AND device AND source) |
OR | One condition is enough | Broad coverage (any of the listed countries) |
Types of filters
Type | What it filters | Example keys |
|---|---|---|
EVENT | Tracker event parameters |
|
HEADERS | HTTP request headers |
|
QUERY | GET parameters of the URL |
|
DATE | Time and date of the click | Days of the week, hours (time schedule) |
DATA | Data from external sources | IP, region, ASN organization, proxy type |
Filter operators
Operator | Logic | Example |
|---|---|---|
| Value is in the list |
|
| Value is NOT in the list |
|
| In range (for numbers/time) |
|
| Field exists and is not empty |
|
| Field is absent |
|
Practical examples of filters
Only mobile Android traffic:
applyMode: AND
├─ user_device IN [mobile]
└─ user_os IN [Android]
Exclude bots:
applyMode: AND
└─ user_bot IN [0]
The value
user_bot= "0" for humans, "1" for bots. Important: it is the string "0", notfalse.
Only FB traffic (has fbclid):
applyMode: AND
└─ fbclid EXIST
Traffic from specific countries (OR):
applyMode: OR
├─ user_country IN [UA]
├─ user_country IN [KZ]
└─ user_country IN [UZ]
White Page: redirect for non-target clicks
White Page is a special URL or embedded site to which clicks that did not pass any of the Stream Sets are redirected.
Why is this needed
Moderators of ad networks (FB, TikTok) click on ads during verification → they need to be sent to a harmless site
Bots and automated scanners → White Page
Traffic from banned regions → White Page
Non-target traffic (desktop when only mobile is needed) → White Page
Types of White Page in Adset.Pro
Type | How it works | When to use |
|---|---|---|
| Click without routing gets 404 | When there is no need for filtering |
| 302 redirect to an external URL | Simple option: link to a neutral site |
| HTML is served inline from the same domain (via S3 + nginx) | For moderation: URL does not change, the page looks like a regular site |
Option
disableTrackWhiteList: If enabled —SOURCE_FILTERevents for clicks that went to the White Page are not recorded in ClickHouse. Useful when you need to exclude junk traffic from statistics.
Bridge Page: For traffic from in-app WebView (FB/Instagram/TikTok/Telegram) the tracker automatically detects WebView and serves Bridge Page instead of PWA. Bridge Page kicks the user into an external browser:
Android →
intent://scheme → ChromeiOS →
x-safari-https://→ SafariThis is necessary because in-app WebView sees the DOM/redirect chain of PWA, which can trigger blocking.
Flows and weights
Inside the Stream Set, each flow (Flow) is assigned a weight — a number that determines the share of traffic. By default, weight = 100.
Example:
Flow A — "1win offer" — weight 70 → receives 70% of clicks
Flow B — "parimatch" — weight 30 → receives 30% of clicks
Weights can be any numbers — the tracker automatically normalizes them into percentages. The selection of Flow occurs randomly considering weights (pickWeighted).
Flow structure
Each Flow contains:
Parameter | Description |
|---|---|
| Linked CPA network (mandatory) |
| Offer within the CPA network (mandatory) |
| Flow in the CPA network — offer URL |
| Linked PWA (optional) |
| PWA type: |
| Linked landing page (optional) |
| Use landing in PWA mode (without a separate PWA page) |
| Page after PWA subscription, before redirecting to the offer |
| Weight of the flow (default 100) |
| Is the flow active (default true) |
Sticky Flow
Sticky Flow solves an important A/B testing problem: one user should not see two different options.
How it works:
Upon the first visit, the tracker selects a Flow (by filters and weights)
The selection is saved in cookie
sf_{campaignId}(format:setId:flowId[:clickId])Upon repeat visit, the tracker reads the cookie and serves the same Flow
The cookie has an almost infinite lifespan
Sticky Flow settings:
Parameter | What it does |
|---|---|
| Enables/disables Sticky Flow |
| Saves the original clickId in cookie — repeat visit gets the same clickId for attribution |
| If |
When to enable:
A/B test of two PWA designs — one user should not see both
Test of two offers — to correctly compare LTV
When user experience consistency is important
A/B testing
Step-by-step scenario of A/B testing two PWAs:
Create a campaign with one Stream Set
Add two Flows with weights 50/50 (weight 100 and 100):
Flow A → PWA-1 ("bonus" design) → CPA-1 → Offer-1
Flow B → PWA-2 ("game" design) → CPA-2 → Offer-2
Enable Sticky Flow — so the user sees only one option
Launch traffic from one source
Wait for at least 100+ installs on each Flow (the more, the more accurate the result)
Compare metrics in statistics with group by
cmp_flow:click_to_install_rate— which PWA converts bettercpa_accept— where there are more depositsltv_per_ftd— where users bring more in the long term
How long to test:
Minimum 3-5 days (to capture different days of the week)
Minimum 100 installs on each variant for basic statistics
For LTV comparison — at least 2-4 weeks (otherwise D1-7 will be incomplete)
Pixels in Stream Sets
You can attach pixels (Facebook CAPI, TikTok Events API, HTTP Get) to each Stream Set:
The pixel triggers on specific events (PWA installation, registration, deposit)
Supported events:
LAND_VIEW,LAND_CLICK,PWA_VIEW,PWA_INSTALL,NOTIFICATION_SUBSCRIBE,CPA_HOLD,CPA_ACCEPT,CPA_REDEP,CPA_DECLINE,CPA_TRASHMapping is configured: tracker event → pixel event
Why attach at the Stream Set level:
Different traffic sources require different pixels (FB CAPI for FB traffic, TikTok Events API for TikTok)
One Stream Set = one source = one pixel — clean and does not conflict
Pixel Override via URL: If you add ?pixel=fb_pixel_id,tiktok_pixel_id to the tracking link, these pixels will override the pixels from the Stream Set settings. Useful for dynamic pixel binding without changing the campaign.
How the tracker processes a click: step by step
A click comes in on the campaign tracking link
The tracker generates
event_click_id(UUID), determines parameters: country, device, user-agent, IP, ASN, proxyIn-app WebView check: If the click is from WebView (FB/Instagram/TikTok/Telegram) — serves Bridge Page to kick into an external browser
Checks Sticky Flow cookie (
sf_{campaignId}):Cookie exists → restores saved Flow (and clickId if
stickyFlowKeepClickId)Cookie does not exist → moves to filters
Applies bot filtering
Sequentially checks Stream Sets top to bottom (applySetFilters)
Finds the first suitable Stream Set (all filters matched by AND/OR mode)
Inside the Stream Set selects Flow by weights (pickWeighted)
Saves Sticky Flow cookie (if enabled)
Records the
SOURCE_CLICKevent with full campaign parametersRedirects the user: Landing → PWA → Offer URL (with paramTemplate)
If no Stream Set fits:
Records
SOURCE_FILTER(ifdisableTrackWhiteList = false)User is redirected to White Page
Recommended filters
For Facebook traffic
Stream Set #1 (main):
applyMode: AND
├─ user_bot IN [0] ← exclude bots
├─ user_device IN [mobile] ← only mobile
├─ user_os IN [Android] ← Android (iOS usually a separate campaign)
└─ fbclid EXIST ← only traffic from FB (has fbclid)
For TikTok traffic
Stream Set #1:
applyMode: AND
├─ user_bot IN [0]
├─ user_device IN [mobile]
└─ ext_click_id EXIST ← has external click_id from TikTok
Universal (anti-fraud)
Any Stream Set:
├─ user_bot IN [0] ← humans
├─ user_proxy IN [0] ← no proxies (optional)
└─ user_device IN [mobile] ← mobile
Frequently Asked Questions
Q: Why did one click end up in the "wrong" flow?
If Sticky Flow is off — this is normal. Distribution by weights is random. With weights 50/50, a specific click has an equal chance of landing in any Flow. To ensure a user always lands in one Flow — enable Sticky Flow. You can check distribution through statistics with group by cmp_flow.
Q: Can I change flow weights without stopping the campaign?
Yes. Changes in Stream Sets are applied instantly — new clicks will use the updated weights. Already recorded events do not change. Changes are made in campaign settings → Stream Set → editing flow weights.
Q: Why is traffic going to the White Page, although I created a Stream Set for this traffic?
Typical reasons:
Order of Stream Sets — another Stream Set higher on the list intercepts traffic (check priority)
AND mode does not match — one of the AND conditions is not met. Example: filter requires
user_device = mobile, but a desktop came inIncorrect values — filter
user_country IN [UK]will not work forGB(ISO code for the UK =GB, notUK)EXIST filter did not work — for example
fbclid EXIST, but traffic is not from FB
Q: How to ensure that a bot does not reach the offer?
Add the filter user_bot IN [0] (the value must be the string "0") to all Stream Sets. Adset.Pro identifies bots through MaxMind + User-Agent analysis. Bot clicks will go to the White Page.
Q: What is landingAsPwa and when to use it?
If landingAsPwa = true, the landing is used as a PWA page — without a separate PWA structure. The user sees the landing, is offered "Add to Home Screen", and after installation, click_id is passed to the offer. Convenient for quick launches without creating a separate PWA.
