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

# Public Functions

> Expose functions without authentication using config: { public: true } for webhooks, callbacks, and public APIs.

## When to use

Use `config: { public: true }` when the caller can't send `X-Jelou-Token`:

* **Webhooks** from external services (Stripe, GitHub, Twilio)
* **Callbacks** from payment gateways
* **Public APIs** accessible from the browser

## In `define()` mode

```typescript theme={null}
export default define({
  description: "Stripe webhook",
  input: z.object({ type: z.string(), data: z.object({ id: z.string() }) }),
  config: { public: true, methods: ["POST"], mcp: false },
  handler: async (input, ctx) => {
    ctx.log("Webhook received", { type: input.type });
    return { received: true };
  },
});
```

## In `app()` mode — per-tool

```typescript theme={null}
export default app({
  tools: {
    webhook: define({
      config: { public: true, mcp: false },
      handler: async (input) => ({ ok: true }),
    }),
    admin: define({
      // protected by default — requires X-Jelou-Token
      handler: async (input) => ({ ok: true }),
    }),
  },
});
```

## Override: app public, tool protected

```typescript theme={null}
export default app({
  config: { public: true },
  tools: {
    publicTool: define({ handler: async () => ({}) }),
    protectedTool: define({
      config: { public: false }, // override: requires token
      handler: async () => ({}),
    }),
  },
});
```

## Security: validate webhooks

With public functions, the platform doesn't validate anything. **Your code is responsible**:

```typescript theme={null}
handler: async (input, ctx, request) => {
  const signature = request.headers.get("x-webhook-signature");
  const secret = ctx.env.get("WEBHOOK_SECRET");

  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) return { error: "invalid_signature" };
  return { acknowledged: true };
}
```

<Warning>
  Never trust a public function without validating the signature. Anyone can send requests to the URL.
</Warning>
