> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jelou.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook receiver

> Receive POST callbacks from external services with payload validation, custom route, and event processing.

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.

```typescript index.ts theme={null}
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

```bash theme={null}
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

<Tip>
  * `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.
</Tip>

<Warning>
  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.
</Warning>
