Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/botpress/botpress/llms.txt

Use this file to discover all available pages before exploring further.

The serve module provides utilities for running integrations and bots locally during development. It creates an HTTP server that handles Botpress runtime requests, allowing you to test your code before deployment.

Import

import { serve, parseBody } from '@botpress/sdk'

serve()

Starts a local HTTP server for handling Botpress runtime requests.
function serve(
  handler: Handler,
  port?: number,
  callback?: (port: number) => void
): Promise<http.Server>
handler
Handler
required
Function that processes incoming requests.
type Handler = (req: Request) => Promise<Response | void>
port
number
default:8072
Port number to listen on. Defaults to 8072.
callback
function
Callback invoked when server starts listening.
(port: number) => void
Returns: Promise<http.Server> - The Node.js HTTP server instance.

Request Type

type Request = {
  body?: string              // Raw request body
  path: string               // URL path
  query: string              // Query string (without ?)
  method: string             // HTTP method (uppercase)
  headers: {                 // Request headers (lowercase keys)
    [key: string]: string | undefined
  }
}

Response Type

type Response = {
  body?: string              // Response body (JSON string)
  headers?: {                // Response headers
    [key: string]: string
  }
  status?: number            // HTTP status code (default: 200)
}

parseBody()

Helper function to parse JSON request body.
function parseBody<T>(req: Request): T
req
Request
required
The request object to parse.
Returns: Parsed JSON body as type T. Throws: Error if body is missing or invalid JSON.

Basic Usage

import { serve } from '@botpress/sdk'

const server = await serve(async (req) => {
  console.log(`${req.method} ${req.path}`)
  
  return {
    status: 200,
    body: JSON.stringify({ message: 'Hello' })
  }
}, 3000)

// Server listening on port 3000

Health Check Endpoint

The serve function automatically provides a /health endpoint:
curl http://localhost:8072/health
# Returns: ok (200 status)
This endpoint is used by Botpress to verify the server is running.

Integration Example

Using serve with an integration:
index.ts
import { serve, parseBody } from '@botpress/sdk'
import { handler } from '.botpress'

// Start local development server
serve(
  async (req) => {
    console.log(`Received ${req.method} request to ${req.path}`)
    
    try {
      // Parse incoming request
      const body = parseBody(req)
      
      // Process with generated handler
      const response = await handler({
        ...body,
        configuration: {
          // Your local configuration for testing
          apiKey: process.env.API_KEY!,
          webhookSecret: process.env.WEBHOOK_SECRET!
        }
      })
      
      return {
        status: 200,
        body: JSON.stringify(response),
        headers: {
          'Content-Type': 'application/json'
        }
      }
    } catch (error) {
      console.error('Handler error:', error)
      return {
        status: 500,
        body: JSON.stringify({ 
          error: error instanceof Error ? error.message : 'Internal error' 
        })
      }
    }
  },
  8072,
  (port) => {
    console.log(`Integration server listening on port ${port}`)
    console.log(`Health check: http://localhost:${port}/health`)
  }
)

Bot Example

Using serve with a bot:
index.ts
import { serve, parseBody } from '@botpress/sdk'
import { handler } from '.botpress'

serve(
  async (req) => {
    console.log(`${req.method} ${req.path}`)
    
    if (req.method !== 'POST') {
      return {
        status: 405,
        body: JSON.stringify({ error: 'Method not allowed' })
      }
    }
    
    try {
      const body = parseBody(req)
      const response = await handler(body)
      
      return {
        status: 200,
        body: JSON.stringify(response),
        headers: { 'Content-Type': 'application/json' }
      }
    } catch (error) {
      return {
        status: 500,
        body: JSON.stringify({ 
          error: error instanceof Error ? error.message : 'Unknown error' 
        })
      }
    }
  },
  3000
)

Webhook Handler Example

Handling external webhooks locally:
webhook.ts
import { serve, parseBody } from '@botpress/sdk'
import crypto from 'crypto'

serve(async (req) => {
  if (req.path === '/webhook' && req.method === 'POST') {
    // Verify webhook signature
    const signature = req.headers['x-webhook-signature']
    const body = req.body!
    
    const expectedSignature = crypto
      .createHmac('sha256', process.env.WEBHOOK_SECRET!)
      .update(body)
      .digest('hex')
    
    if (signature !== expectedSignature) {
      return {
        status: 401,
        body: JSON.stringify({ error: 'Invalid signature' })
      }
    }
    
    // Process webhook
    const payload = parseBody(req)
    console.log('Webhook received:', payload)
    
    // Handle the webhook...
    
    return {
      status: 200,
      body: JSON.stringify({ success: true })
    }
  }
  
  return {
    status: 404,
    body: JSON.stringify({ error: 'Not found' })
  }
})

Request Routing Example

import { serve, parseBody } from '@botpress/sdk'

type Route = {
  method: string
  path: string
  handler: (req: Request) => Promise<Response>
}

const routes: Route[] = [
  {
    method: 'POST',
    path: '/action/sendMessage',
    handler: async (req) => {
      const { channelId, text } = parseBody<any>(req)
      // Handle action...
      return {
        status: 200,
        body: JSON.stringify({ messageId: '123' })
      }
    }
  },
  {
    method: 'POST',
    path: '/webhook/messages',
    handler: async (req) => {
      const payload = parseBody<any>(req)
      // Handle webhook...
      return { status: 200 }
    }
  }
]

serve(async (req) => {
  const route = routes.find(
    r => r.method === req.method && r.path === req.path
  )
  
  if (route) {
    return route.handler(req)
  }
  
  return {
    status: 404,
    body: JSON.stringify({ error: 'Route not found' })
  }
})

Error Handling

The serve function automatically catches errors in the handler:
import { serve } from '@botpress/sdk'

serve(async (req) => {
  // If this throws, serve() catches it and returns 500
  throw new Error('Something went wrong')
})

// Response will be:
// Status: 500
// Body: {"error":"Something went wrong"}
For better error handling:
serve(async (req) => {
  try {
    // Your handler logic
    return { status: 200, body: JSON.stringify({ ok: true }) }
  } catch (error) {
    console.error('Handler error:', error)
    
    return {
      status: 500,
      body: JSON.stringify({
        error: error instanceof Error ? error.message : 'Internal error'
      }),
      headers: { 'Content-Type': 'application/json' }
    }
  }
})

Testing with curl

# Health check
curl http://localhost:8072/health

# POST request
curl -X POST http://localhost:8072/action/test \
  -H "Content-Type: application/json" \
  -d '{"param":"value"}'

# With headers
curl -X POST http://localhost:8072/webhook \
  -H "Content-Type: application/json" \
  -H "x-signature: abc123" \
  -d '{"event":"message"}'

Environment-Specific Configuration

import { serve, parseBody } from '@botpress/sdk'
import { handler } from '.botpress'

const PORT = process.env.PORT ? parseInt(process.env.PORT) : 8072
const IS_PRODUCTION = process.env.NODE_ENV === 'production'

serve(
  async (req) => {
    if (!IS_PRODUCTION) {
      console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`)
    }
    
    const body = parseBody(req)
    const response = await handler(body)
    
    return {
      status: 200,
      body: JSON.stringify(response)
    }
  },
  PORT,
  (port) => {
    console.log(`Server running on port ${port}`)
    console.log(`Environment: ${process.env.NODE_ENV || 'development'}`)
  }
)

Graceful Shutdown

import { serve } from '@botpress/sdk'
import http from 'http'

let server: http.Server

const startServer = async () => {
  server = await serve(async (req) => {
    // Your handler
    return { status: 200 }
  })
  
  // Handle shutdown signals
  process.on('SIGTERM', shutdown)
  process.on('SIGINT', shutdown)
}

function shutdown() {
  console.log('Shutting down gracefully...')
  
  server.close(() => {
    console.log('Server closed')
    process.exit(0)
  })
  
  // Force shutdown after 10 seconds
  setTimeout(() => {
    console.error('Forcing shutdown')
    process.exit(1)
  }, 10000)
}

startServer().catch((error) => {
  console.error('Failed to start server:', error)
  process.exit(1)
})

Node.js Only

The serve function only works in Node.js environments. It will throw an error if called in browser contexts.
import { serve } from '@botpress/sdk'
import { isNode } from 'browser-or-node'

if (!isNode) {
  throw new Error('serve() can only be used in Node.js')
}

await serve(handler)

Best Practices

Use environment variables - Store configuration like API keys, webhook secrets, and port numbers in .env files.
Log requests - Add logging to track incoming requests during development.
Validate signatures - When handling webhooks, always verify signatures to ensure authenticity.
Handle errors gracefully - Catch and log errors, returning appropriate HTTP status codes.

See Also