Skip to main content
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
The Kapso CLI does not manage functions yet. Use the dashboard or API for function create/update/deploy.

Runtime contract

Kapso Cloudflare Worker functions must define:
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

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

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

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

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');
}