import { Agent, getAgentByName, routeAgentRequest } from "agents";
// Agent that handles webhooks for a specific entity
export class WebhookAgent extends Agent<Env> {
async onRequest(request: Request): Promise<Response> {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
// Verify the webhook signature
const signature = request.headers.get("X-Hub-Signature-256");
const body = await request.text();
if (
!(await this.verifySignature(body, signature, this.env.WEBHOOK_SECRET))
) {
return new Response("Invalid signature", { status: 401 });
}
// Process the webhook payload
const payload = JSON.parse(body);
await this.processEvent(payload);
return new Response("OK", { status: 200 });
}
private async verifySignature(
payload: string,
signature: string | null,
secret: string
): Promise<boolean> {
if (!signature) return false;
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signatureBytes = await crypto.subtle.sign(
"HMAC",
key,
encoder.encode(payload)
);
const expected = `sha256=${Array.from(new Uint8Array(signatureBytes))
.map((b) => b.toString(16).padStart(2, "0"))
.join("")}`;
return signature === expected;
}
private async processEvent(payload: unknown) {
// Store event, update state, trigger actions...
}
}
// Route webhooks to the right agent instance
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Webhook endpoint: POST /webhooks/:entityId
if (url.pathname.startsWith("/webhooks/") && request.method === "POST") {
const entityId = url.pathname.split("/")[2];
const agent = await getAgentByName(env.WebhookAgent, entityId);
return agent.fetch(request);
}
// Default routing for WebSocket connections
return (
(await routeAgentRequest(request, env)) ||
new Response("Not found", { status: 404 })
);
}
};