Kapso Functions run on Cloudflare Workers. Use them for webhooks, custom business logic, workflow steps, and agent tools.
Create and deploy
- Open Functions in your project
- Create or edit a function
- Deploy it
- 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 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');
}