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

# Functions

> Deploy serverless JavaScript functions with Kapso

Kapso Functions run on Cloudflare Workers. Use them for webhooks, custom business logic, workflow steps, and agent tools.

## Create and deploy

1. Open **Functions** in your project
2. Create or edit a function
3. Deploy it
4. Attach it to a workflow node, an agent tool, or call its endpoint directly

<Note>
  The Kapso CLI does not manage functions yet. Use the dashboard or API for function create/update/deploy.
</Note>

## Invoke endpoints

Deployed Cloudflare functions expose a Kapso-hosted invoke URL:

```text theme={null}
https://api.kapso.ai/platform/v1/functions/{function_id}/invoke
```

* `public_endpoint: false` (default) keeps the invoke URL private. Send `X-API-Key` with the request.
* `public_endpoint: true` allows invoke requests without an API key. This is only supported for Cloudflare functions.
* Private and unknown functions both return `404` from the invoke route.
* Newly created functions use `invoke_response_mode: passthrough`. Successful invoke responses preserve the function body, success status code, and `Content-Type`.
* Older wrapped functions can be updated to `passthrough` if you need to remove the legacy `{ data: ... }` wrapper.

## Runtime contract

Kapso Cloudflare Worker functions must define:

```javascript theme={null}
async function handler(request, env) {
  const body = await request.json().catch(() => ({}));

  return new Response(JSON.stringify({
    ok: true,
    received: body
  }), {
    headers: { "Content-Type": "application/json" }
  });
}
```

Kapso wraps your code and calls `handler(request, env)`. Do not use `export default`.

## Bindings and env

```javascript theme={null}
async function handler(request, env) {
  const body = await request.json().catch(() => ({}));

  await env.KV.put("last-request", JSON.stringify(body));

  const { results } = await env.DB.prepare(
    "SELECT id, email FROM customers ORDER BY created_at DESC LIMIT 5"
  ).all();

  return new Response(JSON.stringify({
    recentCustomers: results
  }), {
    headers: { "Content-Type": "application/json" }
  });
}
```

* `fetch()` - Make HTTP requests
* `Request`/`Response` - Handle HTTP
* `URL`/`URLSearchParams` - Parse URLs
* `crypto.randomUUID()` - Generate IDs
* `TextEncoder`/`TextDecoder` - Text encoding
* `env.KV` - Persistent key-value storage
* `env.DB` - Project database bound as Cloudflare D1
* `env.YOUR_SECRET` - Access encrypted secrets set in the function page
* Standard JavaScript APIs

## Secrets

Secrets are available as string keys on `env`:

```javascript theme={null}
async function handler(request, env) {
  const apiKey = env.API_KEY;
  const webhookSecret = env.WEBHOOK_SECRET;

  const response = await fetch("https://api.example.com/data", {
    headers: {
      Authorization: `Bearer ${apiKey}`
    }
  });

  return new Response("Success");
}
```

* Secrets must be set in the Kapso web app (function page → Secrets tab)
* Secret names should use UPPERCASE\_WITH\_UNDERSCORES
* Values are encrypted and never exposed after creation
* Functions must be deployed before adding secrets

## KV storage

Each project has its own KV namespace for persistent data storage:

```javascript theme={null}
await env.KV.put("user:123", JSON.stringify(userData));
await env.KV.put("session", token, { expirationTtl: 3600 });

const user = await env.KV.get("user:123", { type: "json" });
const session = await env.KV.get("session");

await env.KV.delete("user:123");

const list = await env.KV.list({ prefix: "user:" });
```

## Functions in flows

Kapso sends workflow data in the JSON request body. Parse it with `await request.json()`.

### Function node

```javascript theme={null}
async function handler(request, env) {
  const body = await request.json();
  const executionContext = body.execution_context || {};
  const vars = executionContext.vars || {};
  const context = executionContext.context || {};

  const user = {
    phoneNumber: context.phone_number,
    plan: vars.plan || "free"
  };

  return new Response(JSON.stringify({
    vars: {
      user
    }
  }), {
    headers: { "Content-Type": "application/json" }
  });
}
```

Payload keys:

* `execution_context`
* `flow_info`
* `flow_events`
* `whatsapp_context` when the run comes from WhatsApp

### Decide node

```javascript theme={null}
async function handler(request, env) {
  const body = await request.json();
  const availableEdges = body.available_edges || [];
  const vars = body.execution_context?.vars || {};

  let nextEdge = availableEdges[0] || "default";

  if ((vars.customer_tier || "").toLowerCase() === "vip" && availableEdges.includes("vip")) {
    nextEdge = "vip";
  }

  return new Response(JSON.stringify({
    next_edge: nextEdge
  }), {
    headers: { "Content-Type": "application/json" }
  });
}
```

`next_edge` is only used by decide nodes.

### Agent function tools

Agent tool arguments are inside `body.input`, not at the root:

```javascript theme={null}
async function handler(request, env) {
  const body = await request.json();
  const input = body.input || {};
  const vars = body.execution_context?.vars || {};

  return new Response(JSON.stringify({
    vars: {
      last_lookup_email: input.email || null
    }
  }), {
    headers: { "Content-Type": "application/json" }
  });
}
```

Payload keys:

* `input` - tool arguments chosen by the agent
* `execution_context`
* `flow_info`
* `flow_events`
* `whatsapp_context` when the run comes from WhatsApp

## Common patterns

### WhatsApp CRM integration with session tracking

```javascript theme={null}
async function handler(request, env) {
  const webhook = await request.json();
  
  // Only process message received events
  if (request.headers.get('X-Webhook-Event') !== 'whatsapp.message.received') {
    return new Response('OK');
  }
  
  // Extract customer info
  const { message, conversation } = webhook;
  const customerPhone = message.phone_number;
  
  // Track customer session in KV
  const sessionKey = `session:${customerPhone}`;
  const session = await env.KV.get(sessionKey, { type: 'json' }) || {
    firstContact: new Date().toISOString(),
    messageCount: 0
  };
  
  session.lastMessage = message.content;
  session.lastContact = new Date().toISOString();
  session.messageCount++;
  
  // Store session with 24-hour expiration
  await env.KV.put(sessionKey, JSON.stringify(session), {
    expirationTtl: 86400 // 24 hours
  });
  
  // Create or update CRM contact with session data
  await fetch('https://api.hubspot.com/contacts/v1/contact', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${env.HUBSPOT_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      properties: [
        { property: 'phone', value: customerPhone },
        { property: 'last_whatsapp_message', value: message.content },
        { property: 'whatsapp_conversation_id', value: conversation.id },
        { property: 'total_messages', value: session.messageCount },
        { property: 'first_contact_date', value: session.firstContact }
      ]
    })
  });
  
  return new Response('OK');
}
```
