Cloudflare Worker Integration
Run ZeroBot at Cloudflare's edge — every request is screened before it reaches your origin server. ~80ms first-touch, sub-10ms cached, fail-open by default.
How it works
One Cloudflare Worker sits between visitors and your origin. For every request, the Worker calls /v3/openapi, gets a verdict, and either lets the request through to your server or blocks it with a 403.
Before you start
- A Cloudflare account (free tier works) with your domain added.
- Your DNS pointing through Cloudflare (orange cloud icon enabled).
- Your ZeroBot license key from /dashboard/settings.
- Your domain listed in Authorized Domains.
Deploy via the Cloudflare dashboard (no CLI)
Create a new Worker
Cloudflare dashboard → Workers & Pages → Create → Create Worker. Name it zerobot-edge and click Deploy.
Paste the Worker source
Click Edit code, replace the default content with the source below, then click Deploy again.
Set your license key
In the Worker, go to Settings → Variables and Secrets, click Add, type ZEROBOT_LICENSE, paste your key, choose Encrypt, save.
Bind the Worker to your domain
Settings → Domains & Routes → Add → Route. Use a pattern like yourdomain.com/* and select your zone. Save.
(Optional) Enable edge caching
Workers & Pages → KV → Create namespace named ZEROBOT_KV. Back in your Worker → Settings → Bindings → Add → KV namespace → variable name ZEROBOT_KV → select the namespace. Verdicts are now cached at the edge for 5 minutes per IP.
Worker source code
Copy this into the Cloudflare Worker editor.
/** * ZeroBot Edge Worker — required env: ZEROBOT_LICENSE (Secret). * Optional: ZEROBOT_KV, ZEROBOT_CACHE_TTL, ZEROBOT_FAIL_MODE. */ const ZEROBOT_API = 'https://zerobot.info/v3/openapi'; const STATIC_RE = /\.(jpe?g|png|gif|webp|avif|svg|ico|css|js|mjs|woff2?|ttf|eot|otf|map|mp4|webm|ogg|mp3|wav|pdf|zip)$/i; export default { async fetch(request, env, ctx) { const url = new URL(request.url); if (STATIC_RE.test(url.pathname)) return fetch(request); if (!env.ZEROBOT_LICENSE) return fetch(request); const ip = request.headers.get('CF-Connecting-IP') || '0.0.0.0'; const ua = request.headers.get('User-Agent') || ''; const domain = url.hostname; const failMode = (env.ZEROBOT_FAIL_MODE || 'open').toLowerCase(); const cacheKey = `zb:${domain}:${ip}`; let verdict = null; if (env.ZEROBOT_KV) { try { verdict = await env.ZEROBOT_KV.get(cacheKey, 'json'); } catch (_) {} } if (!verdict) { try { const apiUrl = new URL(ZEROBOT_API); apiUrl.searchParams.set('license', env.ZEROBOT_LICENSE); apiUrl.searchParams.set('ip', ip); apiUrl.searchParams.set('domain', domain); apiUrl.searchParams.set('useragent', ua); const apiResp = await fetch(apiUrl.toString(), { cf: { cacheTtl: 60, cacheEverything: true } }); if (apiResp.ok) { verdict = await apiResp.json(); if (env.ZEROBOT_KV && verdict) { const ttl = parseInt(env.ZEROBOT_CACHE_TTL || '300', 10); ctx.waitUntil(env.ZEROBOT_KV.put(cacheKey, JSON.stringify(verdict), { expirationTtl: ttl })); } } else if (failMode === 'closed') { return blockResponse({ reason: 'ZeroBot API error' }); } } catch (e) { if (failMode === 'closed') return blockResponse({ reason: 'API unreachable' }); return fetch(request); } } if (verdict?.is_bot) return blockResponse(verdict); const originResp = await fetch(request); const headers = new Headers(originResp.headers); if (verdict) { headers.set('X-ZeroBot-Verdict', String(verdict.reason || 'clean')); headers.set('X-ZeroBot-Score', String(verdict.risk_score || 0)); } return new Response(originResp.body, { status: originResp.status, statusText: originResp.statusText, headers }); } }; function blockResponse(v) { return new Response(blockHtml(v), { status: 403, headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store', 'X-ZeroBot-Verdict': String(v.reason || 'bot'), 'X-ZeroBot-Score': String(v.risk_score || 0) } }); } function blockHtml(v) { /* … minimal HTML block page, see full source */ }
Full source (with the styled block page) is at /cloudflare-worker/worker.js. wrangler.toml: /cloudflare-worker/wrangler.toml.
Deploy via wrangler CLI (recommended for teams)
npm install -g wrangler
mkdir zerobot-edge && cd zerobot-edge
curl -O https://zerobot.info/cloudflare-worker/worker.js
curl -O https://zerobot.info/cloudflare-worker/wrangler.toml
wrangler login
wrangler secret put ZEROBOT_LICENSE
# optional: edge cache
wrangler kv namespace create ZEROBOT_KV
wrangler deploy
Environment variables
| Name | Required | Default | Description |
|---|---|---|---|
ZEROBOT_LICENSE | Yes | — | Your license key. Set as a Secret (encrypted), not a plain variable. |
ZEROBOT_KV | No | — | KV namespace binding. Verdicts cached per IP for ZEROBOT_CACHE_TTL seconds. |
ZEROBOT_CACHE_TTL | No | 300 | How long (in seconds) to cache a verdict for the same IP. |
ZEROBOT_FAIL_MODE | No | open | open — let traffic through if the API is unreachable. closed — block on API errors. |
Troubleshooting
- Every request is allowed: Check that ZEROBOT_LICENSE is set and the Worker route covers your domain. View Worker logs: dashboard → Worker → Logs.
- Legitimate users blocked: Add their IP to Whitelist, or relax filters under Custom Rules.
- Latency too high: Bind a ZEROBOT_KV namespace — caches verdicts per IP at the edge, sub-10ms.
- "Authorized domain" error in logs: Your domain isn't in the account's authorized list. Add it at /dashboard/authorized-domains.