Send a Slack notification when a request to a selected application endpoint completes. The Worker returns the upstream response and sends the notification in the background.
In your Worker project, install @cloudflare/zt-types ↗︎ version 2026.10.1 or later as a development dependency:
npm i -D @cloudflare/zt-typesyarn add -D @cloudflare/zt-typespnpm add -D @cloudflare/zt-typesbun add -d @cloudflare/zt-typesKeep the import type declarations for GatewayMiddlewareContext and GatewayMiddlewareNext in src/index.ts. They describe the Gateway context and continuation and are removed when TypeScript compiles. Import the runtime WorkerEntrypoint class from cloudflare:workers.
Create a Slack incoming webhook ↗︎ for your chosen channel. Store its URL as a Worker secret named SLACK_WEBHOOK_URL:
npx wrangler secret put SLACK_WEBHOOK_URLyarn wrangler secret put SLACK_WEBHOOK_URLpnpm wrangler secret put SLACK_WEBHOOK_URLUse this script as src/index.ts:
import { WorkerEntrypoint } from "cloudflare:workers";
export class GatewayMiddleware extends WorkerEntrypoint {
async handle(request, context, next) {
const response = await next(request);
this.ctx.waitUntil(
this.notify(
`${request.method} ${context.host}: HTTP ${response.status}`,
).catch(() => console.error("Slack notification failed")),
);
return response;
}
async notify(text) {
const result = await fetch(this.env.SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
blocks: [{ type: "section", text: { type: "plain_text", text } }],
}),
signal: AbortSignal.timeout(10_000),
});
await result.body?.cancel();
if (!result.ok) throw new Error("Webhook rejected notification");
}
}import { WorkerEntrypoint } from "cloudflare:workers";
import type {
GatewayMiddlewareContext,
GatewayMiddlewareNext,
} from "@cloudflare/zt-types";
interface Env {
SLACK_WEBHOOK_URL: string;
}
export class GatewayMiddleware extends WorkerEntrypoint<Env> {
async handle(
request: Request,
context: GatewayMiddlewareContext,
next: GatewayMiddlewareNext,
): Promise<Response> {
const response = await next(request);
this.ctx.waitUntil(
this.notify(
`${request.method} ${context.host}: HTTP ${response.status}`,
).catch(() => console.error("Slack notification failed")),
);
return response;
}
async notify(text: string): Promise<void> {
const result = await fetch(this.env.SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
blocks: [{ type: "section", text: { type: "plain_text", text } }],
}),
signal: AbortSignal.timeout(10_000),
});
await result.body?.cancel();
if (!result.ok) throw new Error("Webhook rejected notification");
}
}The message uses Slack's plain text format to prevent request text from creating mentions. It includes the method, host, and response status. It does not send cookies, authorization headers, bodies, or URL query parameters.
Deploy the Worker and create a Forward policy. Match a test user group and a specific application event, such as Host is app.example.com, URL Path is /signup, and HTTP Method is POST.
Trigger one test request and confirm that its response is unchanged and one message appears in Slack. A filtering Block policy prevents both the upstream request and this notification.
This script sends a notification for every invocation, including application error responses. To notify only on success, guard waitUntil() with if (response.ok). For higher-volume policies, add sampling or a queued notification pipeline and account for Slack rate limits ↗︎. A broad host policy can generate a message for every page asset.