index.ts
Copy
Ask AI
import { define, z } from "@jelou/functions";
export default define({
name: "webhook-receiver",
description: "Receives and processes webhooks from external services",
input: z.object({
event: z.string(),
data: z.object({
id: z.string(),
status: z.string(),
metadata: z.record(z.unknown()).optional(),
}),
}),
config: {
path: "/webhooks/events",
methods: ["POST"],
mcp: false,
},
handler: async (input, ctx) => {
ctx.log("Webhook received", {
event: input.event,
id: input.data.id,
requestId: ctx.requestId,
});
const secret = ctx.env.get("WEBHOOK_SECRET");
switch (input.event) {
case "payment.completed": {
ctx.log("Payment completed", { id: input.data.id });
return { acknowledged: true, action: "payment_processed" };
}
case "user.created": {
ctx.log("User created", { id: input.data.id });
return { acknowledged: true, action: "user_synced" };
}
default: {
ctx.log("Unhandled event", { event: input.event });
return { acknowledged: true, action: "ignored" };
}
}
},
});
Local testing
Copy
Ask AI
curl -X POST http://localhost:3000/webhooks/events \
-H "Content-Type: application/json" \
-d '{"event": "payment.completed", "data": {"id": "pay_123", "status": "success"}}'
Why it works this way
config.methods: ["POST"]— rejects GET, PUT, etc. Webhooks are always POST.config.mcp: false— there is no point in exposing a webhook as an AI tool.config.path— fixed route that you configure in the external service.- The
inputschema validates the payload structure before it reaches the handler.
This example omits webhook signature verification for brevity. In production, validate the signature (for example, HMAC-SHA256 with the
x-webhook-signature header) before processing the event.