¿Cuándo usar funciones públicas?
Usa config: { public: true } cuando el llamador no puede enviar un X-Jelou-Token:
- Webhooks de servicios externos (Stripe, GitHub, Twilio)
- Callbacks de pasarelas de pago
- APIs públicas accesibles desde el navegador
En modo define()
import { define, z } from "@jelou/functions";
export default define({
description: "Webhook de Stripe — recibe eventos de pago",
input: z.object({
type: z.string(),
data: z.object({ id: z.string(), amount: z.number() }),
}),
config: {
public: true,
methods: ["POST"],
mcp: false,
},
handler: async (input, ctx) => {
ctx.log("Stripe webhook", { type: input.type, id: input.data.id });
return { received: true };
},
});
Con public: true en define(), la autenticación de plataforma se omite completamente — ninguna ruta requiere token.
En modo app() — global
export default app({
config: { public: true },
tools: {
webhookPagos: define({
description: "Recibe pagos",
input: z.object({ event: z.string() }),
config: { mcp: false },
handler: async (input) => ({ ok: true }),
}),
webhookEnvios: define({
description: "Recibe actualizaciones de envío",
input: z.object({ trackingId: z.string() }),
config: { mcp: false },
handler: async (input) => ({ ok: true }),
}),
},
});
Todos los tools son públicos.
export default app({
tools: {
webhookPagos: define({
description: "Webhook público de pagos",
input: z.object({ event: z.string() }),
config: { public: true, mcp: false },
handler: async (input) => ({ acknowledged: true }),
}),
consultarSaldo: define({
description: "Consulta saldo (requiere token)",
input: z.object({ telefono: z.string() }),
handler: async (input) => ({ saldo: 150.00 }),
}),
},
});
Solo /webhook-pagos es público. /consultar-saldo requiere X-Jelou-Token.
export default app({
config: { public: true },
tools: {
apiPublica: define({
description: "Endpoint público",
input: z.object({}),
handler: async () => ({ status: "ok" }),
}),
adminProtegido: define({
description: "Panel de admin (requiere token)",
input: z.object({ action: z.string() }),
config: { public: false },
handler: async (input) => ({ done: true }),
}),
},
});
public: false en el tool sobrescribe el public: true global.
Health check
/__health reporta el estado public de cada tool:
{
"mode": "app",
"tools": [
{ "key": "webhookPagos", "path": "/webhook-pagos", "public": true },
{ "key": "consultarSaldo", "path": "/consultar-saldo", "public": false }
]
}
OpenAPI
La spec generada en /openapi.json refleja la configuración de seguridad:
- Tools protegidos:
"security": [{ "jelouToken": [] }]
- Tools públicos:
"security": []
Seguridad: validar webhooks
Con funciones públicas, la plataforma no valida nada. Tu código es responsable de verificar la autenticidad:
handler: async (input, ctx, request) => {
const signature = request.headers.get("x-webhook-signature");
const secret = ctx.env.get("WEBHOOK_SECRET");
if (!signature || !secret) {
return { error: "missing_signature" };
}
// Verificar HMAC-SHA256
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"],
);
const body = await request.clone().text();
const sigBytes = Uint8Array.from(atob(signature), (c) => c.charCodeAt(0));
const valid = await crypto.subtle.verify("HMAC", key, sigBytes, encoder.encode(body));
if (!valid) {
ctx.log("Firma inválida", { signature });
return { error: "invalid_signature" };
}
// Firma válida — procesar el evento
ctx.log("Webhook verificado", { event: input.type });
return { acknowledged: true };
}
jelou secrets set mi-webhook WEBHOOK_SECRET=whsec_...
Nunca confíes en una función pública sin validar la firma. Cualquier persona puede enviar peticiones a la URL.
Rutas siempre públicas
Estas rutas nunca requieren token, independientemente de la configuración public:
| Ruta | Descripción |
|---|
/__health | Health check y metadata |
/openapi.json | Especificación OpenAPI |