For the complete documentation index, see llms.txt. This page is also available as Markdown.
Connect Akto with Cloudflare Worker Proxy
Cloudflare is a global network security platform that provides CDN, DDoS protection, and security services. Integrating Cloudflare with Akto enables automatic discovery of all agentic components passing through your Cloudflare infrastructure, helping you maintain continuous visibility and protection of your edge-distributed components.
To connect Akto with Cloudflare, follow these steps -
Step 1: Deploy the Akto Data-Ingestion Service
Before configuring the Cloudflare Worker Traffic Connector, you need to deploy the Akto Data-Ingestion Service. Ensure that the service is running and accessible via a publicly available URL. Set up and configure Akto Traffic Processor. The steps are mentioned here. Ensure this instance is publicly accessible, as it will receive traffic logs from your Cloudflare Worker.
Click Edit code and replace the default script with your Worker code that proxies traffic and mirrors it to Akto using service binding.
Important Notes while editing the Worker code
Replace <DATA_INGESTION_SERVICE> with the URL of the Akto Data-Ingestion Service you deployed in Step 1.
Replace YOUR_AKTO_API_KEY with your API key from Akto. You can find it inside Akto Dashboard โ Settings โ Integrations โ Akto API.
If you are using Cloudflare Service Binding to send traffic to your ingestion service hosted inside a Cloudflare container, use the following line instead of a public URL:
Step 3: Configure Worker Routing
If you'd like to route specific domains or paths through this Worker:
In the Cloudflare Dashboard, go to Workers & Pages.
Under Overview, select your Worker.
Navigate to Settings > Domains & Routes.
Click Add Route.
Select the appropriate zone (domain), and enter a route pattern such as:
This ensures all traffic matching the route is intercepted and mirrored to Akto.
Step 4: Verify the Setup
Confirm that traffic data (requests and responses) are captured on the Akto dashboard under the respective collection.
Check logs of your Worker for any initialization or forwarding messages.
export default {
async fetch(request, env, ctx) {
console.log("๐ Worker handling:", request.method, request.url);
// Detect WebSocket upgrade
const upgradeHeader = request.headers.get("Upgrade") || "";
const isWebSocket = upgradeHeader.toLowerCase() === "websocket";
if (isWebSocket) {
console.log("๐ WebSocket upgrade detected");
// Just proxy the connection
const response = await fetch(request);
// Clone headers only (no body to tee here)
ctx.waitUntil(logTraffic(request, response, env, { isWebSocket: true }));
return response;
}
// Normal HTTP(S) traffic
let requestForFetch, requestForLog;
if (request.body) {
const [req1, req2] = request.body.tee();
requestForFetch = new Request(request, { body: req1 });
requestForLog = new Request(request, { body: req2 });
} else {
requestForFetch = request;
requestForLog = request.clone();
}
const response = await fetch(requestForFetch);
console.log("โฌ ๏ธ Upstream response:", response.status);
let responseForClient, responseForLog;
if (response.body) {
const [res1, res2] = response.body.tee();
responseForClient = new Response(res1, response);
responseForLog = new Response(res2, response);
} else {
responseForClient = response;
responseForLog = response.clone();
}
ctx.waitUntil(logTraffic(requestForLog, responseForLog, env));
return responseForClient;
},
};
async function logTraffic(request, response, env, opts = {}) {
try {
console.log("๐ logTraffic running...");
const reqContentType = request.headers.get("content-type") || "";
const resContentType = response.headers.get("content-type") || "";
const status = response.status;
let reqBody = "";
let resBody = "";
if (!opts.isWebSocket) {
// Only attempt to read bodies for HTTP
reqBody = await readBodyAsText(request);
resBody = await readBodyAsText(response);
if (!(status >= 200 && status < 400)) {
console.log("โ ๏ธ Skipped log: status", status);
return;
}
if (!reqContentType && !resContentType) {
console.log("โ ๏ธ Skipped log: no content-type in request or response");
return;
}
if (!shouldCapture(reqContentType) && !shouldCapture(resContentType)) {
console.log("โ ๏ธ Skipped log: not a target content-type", { reqContentType, resContentType });
return;
}
}
const url = new URL(request.url);
const logEntry = {
path: url.pathname,
method: request.method,
requestHeaders: JSON.stringify(Object.fromEntries(request.headers)),
responseHeaders: JSON.stringify(Object.fromEntries(response.headers)),
requestPayload: reqBody,
responsePayload: resBody,
ip: request.headers.get("cf-connecting-ip") || "127.0.0.1",
time: Math.floor(Date.now() / 1000).toString(),
statusCode: status.toString(),
type: opts.isWebSocket ? "WebSocket" : "HTTP/1.1",
status: response.statusText || "OK",
akto_account_id: "1000000",
akto_vxlan_id: "0",
is_pending: "false",
source: "MIRRORING",
};
console.log("๐ค Sending log entry to webhook...");
const aktoReq = new Request("https://<DATA_INGESTION_SERVICE>/api/ingestData", {
method: "POST",
headers: { "content-type": "application/json", "x-api-key": "YOUR_AKTO_API_KEY" },
body: JSON.stringify({ batchData: [logEntry] }),
});
// await env.<CONTAINER_BINDING_VARIABLE_NAME>.fetch(aktoReq);
await fetch(aktoReq);
console.log("โ Log sent");
} catch (err) {
console.error("โ Log error:", err);
}
}
function shouldCapture(contentType) {
const targets = ["json", "xml", "x-www-form-urlencoded", "soap", "grpc"];
return targets.some((t) => contentType.toLowerCase().includes(t));
}
async function readBodyAsText(obj, maxSize = 64 * 1024) {
try {
const buf = await obj.arrayBuffer();
const bytes = new Uint8Array(buf).slice(0, maxSize);
return new TextDecoder().decode(bytes);
} catch {
return "";
}
}
// use this line to send data internally to data ingestion service
// hosted in your Cloudflare container
await env.<CONTAINER_BINDING_VARIABLE_NAME>.fetch(aktoReq);
// for example
await env.data_inject_worker.fetch(aktoReq);