Skip to main content
Kapso Functions are serverless JavaScript functions that deploy instantly and scale automatically. Built on Cloudflare Workers for high-performance edge computing with global distribution. Perfect for webhooks, API integrations, and custom business logic.

Quickstart

# Set your API key (no project setup needed!)
export KAPSO_API_KEY=your-api-key

# Create a function
echo 'async function handler(request) {
  return new Response("Hello World");
}' > hello.js

# Deploy
kapso functions push hello.js

# List your functions
kapso functions list

Writing functions

async function handler(request, env) {
  // Parse JSON body
  const body = await request.json();

  // Use KV storage for persistence
  await env.KV.put('last-visitor', body.name);
  const lastVisitor = await env.KV.get('last-visitor');

  // Process the data
  const result = {
    message: `Hello ${body.name}!`,
    lastVisitor: lastVisitor,
    timestamp: new Date().toISOString()
  };

  // Return JSON response
  return new Response(JSON.stringify(result), {
    headers: { 'Content-Type': 'application/json' }
  });
}

Available APIs

  • 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.YOUR_SECRET - Access encrypted secrets (set in web app)
  • Standard JavaScript APIs

Environment secrets

Functions can access encrypted environment variables for API keys and sensitive configuration. Secrets are set in the web app on the function’s page (Secrets tab):
async function handler(request, env) {
  // Access your secrets via env parameter
  const apiKey = env.API_KEY;
  const dbUrl = env.DATABASE_URL;
  const webhookSecret = env.WEBHOOK_SECRET;

  // Use them in your function
  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:
// Store data (with optional expiration)
await env.KV.put('user:123', JSON.stringify(userData));
await env.KV.put('session', token, { expirationTtl: 3600 }); // 1 hour

// Retrieve data
const user = await env.KV.get('user:123', { type: 'json' });
const session = await env.KV.get('session');

// Delete data
await env.KV.delete('user:123');

// List keys with prefix
const list = await env.KV.list({ prefix: 'user:' });

Example: WhatsApp message webhook

async function handler(request, env) {
  try {
    // Verify webhook signature for security
    const signature = request.headers.get('X-Webhook-Signature');
    const secret = env.WEBHOOK_SECRET; // Set in Secrets tab in web app
    
    // Parse the webhook payload
    const webhook = await request.json();
    
    // Handle different WhatsApp events
    const eventType = request.headers.get('X-Webhook-Event');
    
    switch (eventType) {
      case 'whatsapp.message.received':
        // Process incoming WhatsApp message
        const { message, conversation, whatsapp_config } = webhook;
        
        console.log(`New message from ${message.phone_number}: ${message.content}`);
        
        // Store conversation history in KV
        const history = await env.KV.get(`conversation:${conversation.id}`, { type: 'json' }) || [];
        history.push({
          timestamp: new Date().toISOString(),
          phone: message.phone_number,
          content: message.content
        });
        await env.KV.put(`conversation:${conversation.id}`, JSON.stringify(history));
        
        // Send to your CRM or ticketing system
        await fetch('https://api.yourcrm.com/messages', {
          method: 'POST',
          headers: { 
            'Authorization': `Bearer ${env.CRM_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            phone: message.phone_number,
            message: message.content,
            whatsapp_message_id: message.whatsapp_message_id,
            conversation_id: conversation.id,
            message_count: history.length
          })
        });
        break;
      
      case 'whatsapp.conversation.created':
        // New conversation started
        console.log(`New conversation with ${webhook.conversation.phone_number}`);
        break;
        
      case 'whatsapp.message.failed':
        // Handle failed message delivery
        console.error(`Message failed: ${webhook.message.id}`);
        break;
    }
    
    // Always return 200 to acknowledge receipt
    return new Response('OK', { status: 200 });
    
  } catch (error) {
    console.error('Webhook processing error:', error);
    // Return 200 even on error to prevent retries for bad data
    return new Response('Error logged', { status: 200 });
  }
}

Using functions in flows

FunctionNode for data operations

Use FunctionNode to fetch, validate, or transform data:
from kapso.builder.flows.nodes import FunctionNode

# Load user data
load_user = FunctionNode(
    id="load_user",
    function_id="abc123-def456-...",  # From: kapso functions list
    save_response_to="user_data"
)
Your function receives the full execution context and can return data or update variables:
export default async function(payload) {
  const { execution_context } = payload;
  const { vars, context } = execution_context;

  // Fetch user from your database
  const user = await fetch(`https://api.example.com/users/${context.phone_number}`);
  const userData = await user.json();

  return {
    name: userData.name,
    tier: userData.subscription_tier,
    lifetime_value: userData.total_spent
  };
}

DecideNode for routing decisions

Use DecideNode in function mode to control routing with your own logic - business rules, data checks, API calls, etc.:
from kapso.builder.flows.nodes import DecideNode
from kapso.builder.flows.nodes.decide import Condition

# Your logic controls routing
router = DecideNode(
    id="tier_router",
    decision_type="function",
    function_id="550e8400-e29b-41d4-a716-446655440000",  # Your routing function UUID
    conditions=[
        Condition(label="vip", description="VIP customer route"),
        Condition(label="premium", description="Premium customer route"),
        Condition(label="standard", description="Standard customer route")
    ]
)
Your function receives execution_context, flow_events, and available_edges array. Run any logic you want:
export default async function(payload) {
  const { execution_context, available_edges } = payload;
  const { vars, context } = execution_context;

  // Run your routing logic - can be anything:

  // Business rules
  const lifetimeValue = vars.user_data?.lifetime_value || 0;
  const accountAge = vars.user_data?.account_age_days || 0;

  // Time-based logic
  const hour = new Date().getHours();
  const isBusinessHours = hour >= 9 && hour < 17;

  // External API (example)
  // const tier = await fetch(`https://api.example.com/tiers/${context.phone_number}`);

  let route;
  if (lifetimeValue > 10000 && accountAge > 365) {
    route = "vip";
  } else if (lifetimeValue > 1000 || accountAge > 90) {
    route = "premium";
  } else {
    route = "standard";
  }

  // Return next_edge matching one of available_edges
  return {
    next_edge: route,
    vars: {
      customer_tier: route,
      routing_timestamp: new Date().toISOString()
    }
  };
}
Key differences:
  • FunctionNode: Execute logic, return data, update variables
  • DecideNode (function mode): Execute logic, choose routing path, optionally update variables

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