Developer
Server-side capture worker
Deploy the optional Cloudflare Worker that recovers the ad click at the edge, before the browser can throw it away. For what server-side capture is and why, see the product page.
The worker code
This is the whole worker. Copy and paste it, no coding needed. It is written to be safe: it forwards the visitor first, wraps everything in a guard, and only sends when there is a real ad click to recover.
export default {
async fetch(request, env, ctx) {
// 1. NEVER BLOCK THE VISITOR. Forward to your site immediately.
// Everything below runs in the background; the page never waits.
const response = fetch(request);
try {
const url = new URL(request.url);
// Skip static assets. Only real page views carry ad attribution.
if (/\.(js|css|png|jpg|jpeg|svg|ico|woff|woff2|gif|webp)$/.test(url.pathname)) {
return response;
}
// 2. COLLECT click IDs + UTMs from the URL, at the edge, before
// iOS / Safari can strip them. This is the data your pixel loses.
// Every major ad platform's click ID, so nothing is missed no
// matter where the traffic comes from.
const clickIds = {
fbclid: url.searchParams.get('fbclid'), // Meta (Facebook/Instagram)
gclid: url.searchParams.get('gclid'), // Google Ads
wbraid: url.searchParams.get('wbraid'), // Google Ads (web-to-app)
gbraid: url.searchParams.get('gbraid'), // Google Ads (app-to-web)
dclid: url.searchParams.get('dclid'), // Google Display / Campaign Manager
msclkid: url.searchParams.get('msclkid'), // Microsoft/Bing Ads
ttclid: url.searchParams.get('ttclid'), // TikTok Ads
li_fat_id: url.searchParams.get('li_fat_id'), // LinkedIn Ads
twclid: url.searchParams.get('twclid'), // Twitter/X Ads
epik: url.searchParams.get('epik'), // Pinterest Ads
rdt_cid: url.searchParams.get('rdt_cid'), // Reddit Ads
sccid: url.searchParams.get('sccid'), // Snapchat Ads
ScCid: url.searchParams.get('ScCid'), // Snapchat Ads (alt casing)
obclid: url.searchParams.get('obclid'), // Outbrain
ttp: url.searchParams.get('ttp'), // TikTok pixel cookie param
irclickid: url.searchParams.get('irclickid'), // Impact / affiliate
};
const utms = {
source: url.searchParams.get('utm_source'),
medium: url.searchParams.get('utm_medium'),
campaign: url.searchParams.get('utm_campaign'),
};
// Only send when there is real attribution to recover. A plain
// visit with no click ID / UTM is left alone. No data collected.
const hasAttribution = Object.values(clickIds).some(Boolean)
|| Object.values(utms).some(Boolean);
if (!hasAttribution) return response;
// 3. SEND to DataCops, after the response, never blocking the page.
// Authenticated with your account key. The isEU flag lets
// DataCops apply the consent gate instantly, with no IP lookup:
// EU visitors' marketing events are held until consent is given.
ctx.waitUntil(fetch(env.DATACOPS_COLLECT_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-cop-key': env.DATACOPS_COP_KEY,
},
body: JSON.stringify({
eventType: 'network_capture',
source: 'cloudflare_worker',
ip: request.headers.get('cf-connecting-ip'),
ua: request.headers.get('user-agent'),
data: {
clickIds,
utms,
cf: {
country: request.cf?.country ?? null,
asOrg: request.cf?.asOrganization ?? null,
isEU: request.cf?.isEUCountry ?? false, // consent signal
botScore: request.cf?.botManagement?.score ?? null,
verifiedBot: request.cf?.botManagement?.verifiedBot ?? false,
},
referrer: request.headers.get('referer'),
pageUrl: url.href,
ts: Date.now(),
},
}),
}).catch(() => {}));
} catch (_) {}
return response;
},
}; Set it up
Your domain needs to be on Cloudflare DNS. It is free and takes about five minutes if you are not on Cloudflare yet.
-
Create a Cloudflare Worker
In Cloudflare, go to Workers and Pages, Create, pick the Hello World starter, Deploy. Then Edit code, delete the sample, paste the code above, and Deploy again.
-
Set two variables
In your Worker, go to Settings, Variables and Secrets, Add. Add DATACOPS_COP_KEY and DATACOPS_COLLECT_URL, using the values from your DataCops dashboard. They stay private and are never visible to visitors.
-
Add a Worker route
In your Worker, go to the Domains tab, Routes, Add a route. Pick your domain and use a pattern that matches all pages, like yourdomain.com followed by /*.
-
Verify
Visit your own site once with an ad click in the link, then click Verify in DataCops. It confirms in under a minute.
Optional extras
- Bot Fight Mode. If your Cloudflare plan has it, turn it on under Security, Bots. The worker already reads its signal, no code change needed.
- Push flagged addresses back to Cloudflare. Connect a scoped token and DataCops can send the addresses it catches back to Cloudflare to block at the edge, from the second flagged visit on.