index.ts
Copy
Ask AI
import { define, z } from "@jelou/functions";
export default define({
name: "webhook-receiver",
description: "Recibe y procesa webhooks de servicios externos",
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 recibido", {
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("Pago completado", { id: input.data.id });
return { acknowledged: true, action: "payment_processed" };
}
case "user.created": {
ctx.log("Usuario creado", { id: input.data.id });
return { acknowledged: true, action: "user_synced" };
}
default: {
ctx.log("Evento no manejado", { event: input.event });
return { acknowledged: true, action: "ignored" };
}
}
},
});
Prueba local
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"}}'
Por qué funciona así
config.methods: ["POST"]— rechaza GET, PUT, etc. Los webhooks siempre son POST.config.mcp: false— no tiene sentido exponer un webhook como herramienta de IA.config.path— ruta fija que configuras en el servicio externo.- El esquema
inputvalida la estructura del payload antes de que llegue al handler.
Este ejemplo omite la verificación de firma del webhook por brevedad. En producción, valida la firma (por ejemplo, HMAC-SHA256 con el header
x-webhook-signature) antes de procesar el evento.