> ## 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.

# Funciones públicas

> Expón funciones sin autenticación con config: { public: true } para webhooks, callbacks y APIs públicas.

## ¿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()`

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

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

## En modo `app()` — per-tool

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

## Override: app público, tool protegido

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

```json theme={null}
{
  "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:

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

```bash theme={null}
jelou secrets set mi-webhook WEBHOOK_SECRET=whsec_...
```

<Warning>
  Nunca confíes en una función pública sin validar la firma. Cualquier persona puede enviar peticiones a la URL.
</Warning>

## 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  |
