Skip to main content
You receive POST callbacks from any external service (payment gateways, GitHub, CRMs, etc.), validate the payload, and process the event. Pattern: custom route + POST only + MCP disabled + payload validation.
index.ts
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

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 input schema 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.