Skip to main content
Before you start
Language

Cloudflare Workers

Add cside Device Intelligence through a customer-managed Cloudflare Worker that injects the browser script into HTML responses.

Use this guide when you want to deploy cside Device Intelligence at the Cloudflare edge. This is a customer-managed Worker recipe. cside does not need to manage your Cloudflare account for this setup.

Before you start

You need:

  • A Cloudflare zone that routes traffic for the pages you want to fingerprint
  • Permission to create or edit Workers and Worker routes
  • The cside Device Intelligence script URL for your domain, which you can find in the cside dashboard
  • A backend-only Device Intelligence API key generated in the cside dashboard
  • A backend endpoint that receives the token string extracted from the JSON response returned by /client
Check existing routes

Cloudflare allows only one Worker on a matching route. If another Worker already handles the same path, merge the logic or choose a narrower test route.

Start with a limited route, such as /login* or /checkout*, before routing the Worker across the full site. Keep the Worker fail-open so a cside or Worker issue does not block page delivery.

See Cloudflare’s Workers Routes guide for how to apply a Worker to a route.

Worker example

Store your cside script URL as CSIDE_FINGERPRINT_SCRIPT_URL in the Worker environment.

function getFingerprintBootstrap(scriptURL) {
  return `<script>
(function () {
  const calls = [];
  let error;
  let timeoutID;

  function fallback(externalIds) {
    return error
      ? Promise.reject(error)
      : new Promise((resolve, reject) => {
          calls.push({ externalIds, resolve, reject });
        });
  }

  function rejectAll(nextError) {
    if (error) {
      return;
    }

    error = nextError;
    while (calls.length > 0) {
      calls.shift().reject(nextError);
    }
  }

  function flush(sendClientTelemetry) {
    while (calls.length > 0) {
      const call = calls.shift();
      Promise.resolve()
        .then(() => sendClientTelemetry(call.externalIds))
        .then(call.resolve, call.reject);
    }
  }

  window.sendClientTelemetry = window.sendClientTelemetry || fallback;

  const script = document.createElement("script");
  script.async = true;
  script.src = ${JSON.stringify(scriptURL)};
  script.referrerPolicy = "origin";
  script.setAttribute("data-src", "6");

  const fail = (nextError) => {
    clearTimeout(timeoutID);
    rejectAll(nextError);
  };

  script.onerror = () =>
    fail(new Error("cside client.js failed to load"));
  script.onload = () => {
    clearTimeout(timeoutID);
    const sendClientTelemetry = window.sendClientTelemetry;
    if (
      typeof sendClientTelemetry !== "function" ||
      sendClientTelemetry === fallback
    ) {
      rejectAll(new Error("cside telemetry unavailable"));
      return;
    }
    flush(sendClientTelemetry);
  };

  timeoutID = setTimeout(
    () => fail(new Error("cside client.js timed out while loading")),
    10000,
  );
  (document.head || document.documentElement).appendChild(script);
})();
</script>`;
}

export default {
  async fetch(request, env) {
    const response = await fetch(request);
    const contentType = response.headers.get("content-type") || "";

    if (!contentType.includes("text/html")) {
      return response;
    }

    try {
      return new HTMLRewriter()
        .on("head", {
          element(element) {
            element.append(
              getFingerprintBootstrap(env.CSIDE_FINGERPRINT_SCRIPT_URL),
              { html: true },
            );
          },
        })
        .transform(response);
    } catch {
      return response;
    }
  },
};

Token exchange

The Worker only injects the browser script. It does not create a session fingerprint by itself. Your application still calls sendClientTelemetry(externalIds?), receives { token } from /client, and sends that session token to your backend.

Generate a Device Intelligence API key in the cside dashboard before you wire the backend exchange. The key starts with cside_tgatv1_, is shown only once, and must be stored as a server-side secret. Do not embed it in browser code.

Call sendClientTelemetry from your page or app code after the script loads, then read the JSON response returned by /client:

const result = await sendClientTelemetry({
  email: "user@example.com",
  accountId: "1234567890",
});

if (result.errors) {
  console.error(result.errors);
  throw new Error("Telemetry request failed.");
}

const { token: sessionToken } = result;

if (!sessionToken) {
  throw new Error("No fingerprint session token was returned.");
}

await fetch("/api/fingerprinting", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ sessionToken }),
});

/client returns JSON in the shape { "token": "..." }. You can also call sendClientTelemetry() with no arguments when you do not need to attach externalIds.

Your backend exchanges the session token with cside by calling the authenticated verification endpoint:

curl https://api.cside.com/token/v2/verify \
  --request POST \
  --header "Authorization: Bearer $CSIDE_FINGERPRINT_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"token":"'"$CSIDE_FINGERPRINT_SESSION_TOKEN"'"}'

Example backend handler:

app.post("/api/fingerprinting", async (request, reply) => {
  const { sessionToken } = request.body;

  const csideResponse = await fetch("https://api.cside.com/token/v2/verify", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CSIDE_FINGERPRINT_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ token: sessionToken }),
  });

  const payload = await csideResponse.json();

  if (!csideResponse.ok) {
    throw new Error(payload.error_message || "Fingerprint exchange failed.");
  }

  reply.code(csideResponse.status).send(payload);
});

See Retrieving datapoints for retrieval options.

Operational notes

  • Use narrow routes for the first rollout
  • Keep the Worker fail-open
  • Do not inject the script into non-HTML responses
  • Confirm your CSP allows the cside script URL and token exchange endpoint
  • Keep the Device Intelligence API key only in backend secrets
  • Move from test paths to full coverage after you see datapoints in cside
Was this page helpful?