back to tutorials

How to Use Channel-Based Function Calling with Dispatch

Learn how to implement channel-based function calling using the dispatch() method for background AI conversations. This tutorial covers static results, channel-based results, and handling function execution asynchronously.

This tutorial demonstrates how to implement channel-based function calling using the dispatch() method in ChatBotKit. This approach is ideal for background AI tasks where the conversation runs asynchronously on the server.

Learning Objectives

By the end of this tutorial, you will be able to:

  • Use the dispatch() method to start background conversations
  • Subscribe to conversation events via channels
  • Implement static function results for predetermined responses
  • Implement channel-based function results for dynamic execution
  • Handle the waitForChannelMessageBegin event to execute functions

Prerequisites

  • Node.js 18+ installed
  • A ChatBotKit account with an API secret
  • Basic understanding of async/await and event streams

Estimated time: 20 minutes

Understanding Dispatch vs Complete

ChatBotKit offers two primary ways to run conversations:

MethodExecutionBest For
dispatch()Background/asyncLong-running tasks, webhooks, background processing
complete()Inline/blockingInteractive chat, real-time responses

The dispatch() method starts a conversation that runs in the background on the server. It immediately returns a channelId that you can subscribe to for receiving events.

Step 1: Set Up Your Project

Create a new Node.js project and install the ChatBotKit SDK:

mkdir function-calling-example
cd function-calling-example
npm init -y
npm install @chatbotkit/sdk dotenv

Create a .env file with your API secret:

CHATBOTKIT_API_SECRET=your_api_secret_here

Step 2: Create the Main Script

Create a file called index.js:

import * as dotenv from 'dotenv'
import { ChatBotKit } from '@chatbotkit/sdk/index.js'
import { randomBytes } from 'node:crypto'

dotenv.config()

async function main() {
  const client = new ChatBotKit({
    secret: process.env.CHATBOTKIT_API_SECRET,
  })

  // Generate unique channel IDs (must be at least 16 characters)
  const weatherChannelId = `weather-${randomBytes(16).toString('hex')}`

  console.log('Starting background conversation...')
}

main().catch(console.error)

Step 3: Define Functions with Different Result Types

ChatBotKit supports two types of function results:

Static Results

Static results return predetermined data immediately to the AI:

{
  name: 'get_current_time',
  description: 'Get the current time for a specified timezone',
  parameters: {
    type: 'object',
    properties: {
      timezone: {
        type: 'string',
        description: 'The timezone, e.g. America/New_York',
      },
    },
    required: ['timezone'],
  },
  // Static result - returned immediately
  result: {
    data: {
      time: '10:30 AM',
      date: 'Monday, January 26, 2026',
      timezone: 'America/New_York',
    },
  },
}

Channel-Based Results

Channel-based results require you to execute the function and publish the result:

{
  name: 'get_weather',
  description: 'Get the current weather for a location',
  parameters: {
    type: 'object',
    properties: {
      location: {
        type: 'string',
        description: 'The city name, e.g. New York',
      },
    },
    required: ['location'],
  },
  // Channel result - you must publish the result
  result: {
    channel: weatherChannelId,
  },
}

Step 4: Dispatch the Conversation

Use dispatch() to start the background conversation:

const { channelId } = await client.conversation.dispatch(null, {
  model: 'claude-4.5-sonnet',

  backstory: 'You are a helpful assistant that provides weather and time information.',

  messages: [
    {
      type: 'user',
      text: 'What time is it in New York, and what is the weather like there?',
    },
  ],

  functions: [
    // ... your function definitions
  ],
})

console.log(`Conversation dispatched. Channel: ${channelId}`)

Step 5: Subscribe to Events

Subscribe to the channel to receive events as the conversation progresses:

for await (const event of client.channel.subscribe(channelId).stream()) {
  if (event.type !== 'message') {
    continue
  }

  const eventData = event.data

  // Handle different event types based on eventData.type
  if (eventData.type === 'waitForChannelMessageBegin') {
    // Function execution required
  }

  if (eventData.type === 'message') {
    // Bot message or activity
  }

  if (eventData.type === 'result') {
    // Conversation completed
    break
  }
}

Step 6: Handle Channel Function Calls

When you receive a waitForChannelMessageBegin event, execute the function and publish the result:

if (eventData.type === 'waitForChannelMessageBegin') {
  const waitData = eventData.data

  const functionName = waitData.function.name
  const functionArgs = waitData.function.args
  const resultChannel = waitData.channel

  console.log(`Function call: ${functionName}`)
  console.log(`Arguments: ${JSON.stringify(functionArgs)}`)

  // Execute your function
  let functionResult
  if (functionName === 'get_weather') {
    functionResult = await fetchWeatherData(functionArgs.location)
  }

  // Publish the result back to the channel
  await client.channel.publish(resultChannel, {
    message: functionResult,
  })

  console.log('Result published')
}

Complete Example

Here's the complete working example:

import * as dotenv from 'dotenv'
import { ChatBotKit } from '@chatbotkit/sdk/index.js'
import { randomBytes } from 'node:crypto'

dotenv.config()

async function main() {
  const client = new ChatBotKit({
    secret: process.env.CHATBOTKIT_API_SECRET,
  })

  const weatherChannelId = `weather-${randomBytes(16).toString('hex')}`

  // Dispatch the conversation
  const { channelId } = await client.conversation.dispatch(null, {
    model: 'claude-4.5-sonnet',
    backstory: 'You are a helpful assistant that provides weather and time information.',
    messages: [
      {
        type: 'user',
        text: 'What time is it in New York, and what is the weather like there?',
      },
    ],
    functions: [
      {
        name: 'get_current_time',
        description: 'Get the current time for a specified timezone',
        parameters: {
          type: 'object',
          properties: {
            timezone: { type: 'string', description: 'The timezone' },
          },
          required: ['timezone'],
        },
        result: {
          data: { time: '10:30 AM', date: 'Monday, January 26, 2026' },
        },
      },
      {
        name: 'get_weather',
        description: 'Get the current weather for a location',
        parameters: {
          type: 'object',
          properties: {
            location: { type: 'string', description: 'The city name' },
          },
          required: ['location'],
        },
        result: { channel: weatherChannelId },
      },
    ],
  })

  // Subscribe to events
  for await (const event of client.channel.subscribe(channelId).stream()) {
    if (event.type !== 'message') continue

    const eventData = event.data

    if (eventData.type === 'waitForChannelMessageBegin') {
      const { function: fn, channel } = eventData.data

      // Execute and publish result
      const result = { temperature: 42, conditions: 'partly cloudy' }
      await client.channel.publish(channel, { message: result })
    }

    if (eventData.type === 'message' && eventData.data.type === 'bot') {
      console.log(`Bot: ${eventData.data.text}`)
    }

    if (eventData.type === 'result') {
      console.log('Conversation completed')
      break
    }
  }
}

main().catch(console.error)

Troubleshooting

Channel ID Too Short

Channel IDs must be at least 16 characters. Use randomBytes(16).toString('hex') to generate sufficiently long IDs.

Missing Function Result

If you don't publish a result to the channel, the conversation will timeout. Always handle waitForChannelMessageBegin events.

Event Structure

Remember that events from channel.subscribe() come wrapped: the actual event type is in event.data.type, not event.type.

Next Steps