ReTarget.gg
Guides & recipes

Cloudflare Workers, Pages & Zaraz

Add ReTarget.gg to a site behind Cloudflare: inject the widget from a Worker, drop it into a Pages project, or fire it from Zaraz. Server-trigger the Trigger Widget from a Worker.

5 min readReviewed Apr 27, 2026

Cloudflare is three different products that all solve this differently. This recipe covers the three paths we see most:

  1. Workers: inject the widget into every HTML response at the edge, using HTMLRewriter. Zero changes to your origin.
  2. Pages: a static or SSR project hosted on Cloudflare Pages. Drop the tag into your template.
  3. Zaraz: tag-manager style; paste the same one-liner into a Custom HTML tag.

If you use Cloudflare Workers for an advertiser-side backend (e.g. KYC or payment), skip to Server-triggered Trigger Widget from a Worker.

Public key + website key required

Option 1: Inject from a Worker with HTMLRewriter

Use this when your origin is yours-to-break. The Worker sits in front, gets the HTML back, and streams a rewritten response with our tag appended to <head>. Nothing on the origin changes.

src/worker.js
export default {
async fetch(request, env) {
  // Fetch the origin response, untouched.
  const response = await fetch(request);

  // Only rewrite HTML. Pass-through everything else (images, JSON, …).
  const contentType = response.headers.get("content-type") || "";
  if (!contentType.toLowerCase().includes("text/html")) {
    return response;
  }

  return new HTMLRewriter()
    .on("head", new TagInjector(env))
    .transform(response);
},
};

class TagInjector {
constructor(env) {
  this.env = env;
}
element(head) {
  // Keep this string in sync with the tag the dashboard generates.
  // The guard on data-website makes it safe to fire on every request
  // (even on repeated edge passes with HTMLRewriter in dev).
  const tag = `<script>
(function () {
if (document.querySelector('script[data-website="{{WEBSITE_KEY}}"]')) return;
var s = document.createElement('script');
s.src = 'https://cdn.retarget.gg/widget.js';
s.async = true;
s.setAttribute('data-pub', '{{PUBLIC_KEY}}');
s.setAttribute('data-website', '{{WEBSITE_KEY}}');
s.setAttribute('data-api', 'https://api.retarget.gg');
(document.head || document.documentElement).appendChild(s);
})();
</script>`;
  head.append(tag, { html: true });
}
}

Wire it up with wrangler.toml:

wrangler.toml
name = "retarget-edge"
main = "src/worker.js"
compatibility_date = "2025-09-01"

# Route it in front of your site.
[[routes]]
pattern = "yoursite.com/*"
zone_name = "yoursite.com"

Deploy:

npx wrangler deploy

Why HTMLRewriter, not search-and-replace

HTMLRewriter streams the response: no buffering, no memory spike on large pages, and no risk of injecting the tag twice if your origin already contains <head> fragments in odd places. It also plays nicely with Cloudflare's streaming to the browser, so time-to-first-byte isn't affected.

Bot-only exclusions

If you want to skip the widget for known bots (SEO, monitoring, health-checks): keep the origin HTML clean for them: gate the rewrite on the User-Agent:

Skip bots
const BOT_RE = /(bot|crawler|spider|pingdom|uptimerobot|lighthouse|GoogleOther)/i;

export default {
async fetch(request) {
  const ua = request.headers.get("user-agent") || "";
  const response = await fetch(request);

  if (BOT_RE.test(ua)) return response;

  const ct = response.headers.get("content-type") || "";
  if (!ct.toLowerCase().includes("text/html")) return response;

  return new HTMLRewriter().on("head", new TagInjector()).transform(response);
},
};

Option 2: Cloudflare Pages

If your site is hosted on Cloudflare Pages (static, Next.js, Astro, Nuxt, Remix, SvelteKit…), the widget is just a <script> tag in your layout/template. Same tag the dashboard shows:

Paste in the <head> of your root layout
<script>
(function () {
  if (document.querySelector('script[data-website="{{WEBSITE_KEY}}"]')) return;
  var s = document.createElement('script');
  s.src = 'https://cdn.retarget.gg/widget.js';
  s.async = true;
  s.setAttribute('data-pub', '{{PUBLIC_KEY}}');
  s.setAttribute('data-website', '{{WEBSITE_KEY}}');
  s.setAttribute('data-api', 'https://api.retarget.gg');
  (document.head || document.documentElement).appendChild(s);
})();
</script>

For framework-specific placement (Next.js App Router, etc.), see the Next.js recipe.

Pages + Workers

Pages projects can attach a Worker as Pages Functions (functions/_middleware.js). If you want to keep the tag out of your source code, use the HTMLRewriter approach from Option 1 in a middleware function: same code, same behavior.

Option 3: Cloudflare Zaraz

Zaraz is Cloudflare's built-in tag manager. Pasting our tag is the same short story as Google Tag Manager, covered in full here: GTM recipe.

Short version for Zaraz:

  1. Open Zaraz → Tools → Add a tool → Custom HTML

    Pick Custom HTML as the tool type.

  2. Paste the tag

    Drop the exact same snippet shown in Option 2. Set the trigger to Page view (all pages).

  3. Publish

    Zaraz deploys the tag to your zone immediately: no Save → Publish cycle like GTM.

Server-triggered Trigger Widget from a Worker

If you're running advertiser-side logic in a Worker (for example, a KYC check that denies a user), you can open the Trigger Widget overlay on the user's page without calling back through your origin.

The two endpoints you need:

POST/v1/widget-decline/sessionauth: public
POST/v1/widget-decline/triggerauth: integration-secret
Server-triggered Trigger Widget (Worker)
export default {
async fetch(request, env) {
  // …your KYC / risk check…
  const declined = await runKyc(request);
  if (!declined) return new Response("approved", { status: 200 });

  // 1. Fire the trigger from the Worker. The browser is already polling
  //    /v1/widget-decline/session/{sessionId}: the next poll will pick
  //    it up and render the overlay.
  await fetch("https://api.retarget.gg/v1/widget-decline/trigger", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${env.RETARGET_INTEGRATION_SECRET}`,
    },
    body: JSON.stringify({
      sessionId: request.headers.get("x-retarget-session"),
      reason: "kyc_failed",
    }),
  });

  return new Response("declined", { status: 200 });
},
};

The client-side widget-decline.js emits the session ID when it mounts. Capture it and forward with your Worker request. Full client-side setup: Trigger Widget integration.

Integration secret: not the public key

/v1/widget-decline/trigger requires a server-only integration secret (bearer token). Never ship it to the browser. Put it in wrangler secret put RETARGET_INTEGRATION_SECRET and access it via env.RETARGET_INTEGRATION_SECRET.

Cache considerations

If your Pages project or Worker sits behind Cloudflare's cache:

  • The widget is a static JS file served from https://cdn.retarget.gg/widget.js with a short Cache-Control. You don't need to cache it yourself.
  • The API (api.retarget.gg) responses set Cache-Control: no-store: decisions are per-request. Don't add Cache-Rules that cache them.
  • Your origin HTML caches as normal: the HTMLRewriter injection from Option 1 re-runs on every HTML response, but since widget.js itself is a tiny external script, there's no duplicate-content concern.

Verify

  1. Visit your site with DevTools open

    Check the Network tab for widget.js and a GET /v1/decision call to api.retarget.gg. Both should be 200.

  2. Check a blocked country

    On localhost add data-dev-country="US" to the injected script tag to simulate a blocked visitor (see Debugging). On production, spin up a quick VPN test.

  3. Confirm events in the dashboard

    Open Websites → your site → Analytics. A successful page-load produces a snippet-ping; a blocked user produces an impression per card. If nothing shows after 2 minutes, revisit Debugging.

Need help with setup?

Send us your website stack, target regions, and whether you are installing Geo Popup or Decline Popup.