Custom Tools - Complete Documentation
Custom Tools is an advanced feature that allows chatbots to call external APIs via webhooks. This allows your chatbot to perform actions in external systems - from adding leads to CRM, through sending notifications, to updating data in databases. This documentation contains everything you need to create and deploy your own tools.
Table of Contents
- 1What are Custom Tools?
- 2Example Use Cases
- 3How does it work?
- 4Creating a Custom Tool
- 5Parameter Definition (OpenAI Schema)
- 6Webhook URL Verification
- 7Webhook Request Format
- 8Webhook Security (HMAC)
- 9Webhook Response Format
- 10Execution Mode
- 11Advanced Features
- 12Testing and Debugging
- 13Best Practices
- 14Troubleshooting
What are Custom Tools?
Custom Tools are functions that a chatbot can call during a conversation with a user. Each tool is a webhook defined by you that will be called when the chatbot determines it is needed.
Key Features
- Integration with OpenAI Function Calling - Tools are automatically exposed to the AI model as functions
- Webhook-based - Each tool call is an HTTP POST request to your endpoint
- Secure - HMAC-SHA256 signing, rate limiting, retry logic, encrypted secrets
- Flexible - Full control over parameters, request/response mapping, custom headers
- Monitored - Complete execution history, analytics, performance metrics
Basic Flow
- User writes a message to the chatbot (e.g., "Add me to the newsletter")
- AI model analyzes the message and decides that a tool call is needed
- System sends a webhook to your endpoint with parameters extracted by AI
- Your system processes the request (e.g., adds email to database)
- You return a response with the operation result
- Chatbot delivers the result to the user in a natural way
create_appointment with parameters
date: "2024-01-10" and time: "15:00", and then inform the user of the result.
Example Use Cases
Custom Tools open unlimited integration possibilities. Here are the most popular use cases:
🎯 Marketing & Sales
- Lead Collection - automatic saving of contacts to CRM (Salesforce, HubSpot, Pipedrive)
- Newsletter Management - subscribing/unsubscribing from mailing lists
- Quote Generation - creating personalized quotes based on conversation
- Conversion Tracking - sending events to Google Analytics, Facebook Pixel
📅 Reservations & Calendars
- Appointment Scheduling - integration with Calendly, Google Calendar, booking systems
- Availability Check - verification of available time slots in real-time
- Reservation Management - canceling, rescheduling, confirming meetings
🛒 E-commerce
- Inventory Check - verification of product availability
- Order Creation - initiating the purchase process
- Delivery Status - shipment tracking
- Product Recommendations - personalized suggestions based on preferences
💬 Communication
- Sending Notifications - integration with Slack, Microsoft Teams, Discord
- SMS/Email alerts - sending notifications via Twilio, SendGrid
- Escalation to Agent - transferring conversation to a human
📊 Analytics & Data
- Data Retrieval - searching information in databases
- Report Generation - creating summaries on demand
- Record Update - modifying data in external systems
🎫 Customer Support
- Ticket Creation - integration with Zendesk, Freshdesk, JIRA
- Checking Ticket Status - verification of problem resolution progress
- Knowledge Base - searching in documentation, FAQ
How does it work? - System Architecture
Understanding the architecture will help you better utilize Custom Tools and debug potential issues.
Custom Tool Lifecycle
1. Tool Definition
You create a tool in the ChatbotAssistant panel, defining:
- Function name (e.g.,
create_lead) - Description - clear explanation of when AI should use this tool
- Parameters - OpenAI schema defining arguments (JSON Schema)
- Webhook URL - endpoint in your system
2. Registration in OpenAI
The tool is automatically registered in the AI model as a function. When the chatbot talks with a user, the model sees available tools and can decide to call them based on the conversation context.
3. Intent Detection by AI
The model analyzes the user's message and conversation context. If it determines that a tool is needed
(e.g., user asks about a product, and you have a tool search_products), AI:
- Selects the appropriate tool
- Extracts needed parameters from the user's message
- Generates function call with arguments
4. Webhook Execution
Our system:
- Checks rate limit - whether the call limit has been exceeded
- Creates execution log - saves all details to the database
- Builds payload - using optional template or default structure
- Generates HMAC signature - signs the request (if webhook secret is configured)
- Sends HTTP POST to your endpoint with timeout and retry logic
- Processes response - maps according to response_mapping (if configured)
5. Processing by Your System
Your endpoint:
- Verifies HMAC signature (if using webhook secret)
- Processes request - executes business logic
- Returns response in JSON format
6. Conversation Continuation
The chatbot receives the tool result and continues the conversation, delivering information to the user in a natural way (e.g., "Great! I've added you to our newsletter. You will receive messages at [email protected]").
Tool Statuses
| Status | Description | Available Actions |
|---|---|---|
| DRAFT | Tool created but not verified | Edit, verify, delete |
| Webhook URL verification in progress | Waiting for result | |
| VERIFIED | URL verified, ready for activation | Activate, edit, test |
| ACTIVE | Tool active and available for chatbot | Deactivate, test, analyze |
| INACTIVE | Temporarily disabled by user | Activate, edit |
| FAILED | Verification failed | Check error, edit URL, retry verification |
Creating a Custom Tool - Step by Step
Creating your first Custom Tool is simple. Follow these steps:
Step 1: Go to Tool Management
- Log in to the ChatbotAssistant panel
- Select the chatbot for which you want to add a tool
- In the chatbot options click "Manage Custom Tools"
- Click the button "Create new tool"
Step 2: Fill in Basic Information
| Field | Required | Description | Example |
|---|---|---|---|
| Function Name | Yes | Name used by AI to call the tool. Only letters, numbers and underscore. 3-100 characters. | create_newsletter_subscription |
| Description | Yes | Clear description of when AI should use this tool. This is crucial - the AI model decides based on the description! | "Subscribes user to newsletter. Use when user wants to subscribe, receive updates or newsletter." |
| Webhook URL | Yes | HTTPS endpoint in your system that will handle the call. Must be accessible from the internet. | https://api.yourcompany.com/webhooks/newsletter |
| Webhook Secret | No | Secret key for signing requests (HMAC-SHA256). Strongly recommended for security! | whsec_... (generate a secure key) |
Step 3: Define Parameters
Parameters specify what information AI should extract from the conversation and pass to your webhook. We use the OpenAI Function Calling format (JSON Schema).
Detailed description in the next section → Parameter Definition
Step 4: Advanced Configuration (Optional)
- Timeout - maximum response wait time (configurable)
- Retry policy - number of retry attempts in case of error (configurable)
- Rate limiting - limit on number of calls per time unit
- Custom headers - additional HTTP headers (JSON format)
- Request template - Jinja2 template for building payload
- Response mapping - webhook response mapping
- Fallback response - response returned in case of error
Step 5: Save and Verify
- Click "Save tool"
- System will create tool with status DRAFT
- Click "Verify URL" - system will send a test request to your endpoint
- If verification succeeds, status will change to VERIFIED
- Click "Activate" to make the tool available for the chatbot
Parameter Definition (OpenAI Schema)
Parameters define what data AI should extract from the conversation and pass to your webhook. We use the standard JSON Schema compatible with OpenAI Function Calling API.
Basic Structure
{
"type": "object",
"properties": {
"parameter_name": {
"type": "string",
"description": "Parameter description for AI"
}
},
"required": ["parameter_name"]
}Parameter Types
| Type | Description | Example value |
|---|---|---|
string |
Text of any length | "John Smith" |
number |
Number (integer or float) | 42, 3.14 |
integer |
Integer number | 100 |
boolean |
Boolean value | true, false |
array |
List of values | ["tag1", "tag2"] |
object |
Nested object | {"street": "...", "city": "..."} |
Example 1: Newsletter subscription
{
"type": "object",
"properties": {
"email": {
"type": "string",
"description": "User's email address to subscribe to newsletter"
},
"full_name": {
"type": "string",
"description": "User's full name (optional)"
},
"interests": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of user's interest categories (e.g., technology, marketing)"
}
},
"required": ["email"]
}Example 2: Table reservation
{
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "Reservation date in YYYY-MM-DD format"
},
"time": {
"type": "string",
"description": "Reservation time in HH:MM format"
},
"guests_count": {
"type": "integer",
"description": "Number of people (minimum 1)"
},
"phone": {
"type": "string",
"description": "Contact phone number"
},
"special_requests": {
"type": "string",
"description": "Special requests or notes (optional)"
}
},
"required": ["date", "time", "guests_count", "phone"]
}Example 3: Creating a lead in CRM
{
"type": "object",
"properties": {
"email": {
"type": "string",
"description": "Potential client's email address"
},
"company_name": {
"type": "string",
"description": "Company name"
},
"industry": {
"type": "string",
"enum": ["IT", "Finance", "Healthcare", "Retail", "Other"],
"description": "Company industry"
},
"budget_range": {
"type": "string",
"enum": ["< 10k", "10k-50k", "50k-100k", "> 100k"],
"description": "Estimated project budget"
},
"contact_preference": {
"type": "string",
"enum": ["email", "phone", "meeting"],
"description": "Preferred contact method"
},
"notes": {
"type": "string",
"description": "Additional notes from conversation"
}
},
"required": ["email", "company_name"]
}Advanced Properties
| Property | Description | Example |
|---|---|---|
enum |
Restriction to specific values | "enum": ["small", "medium", "large"] |
minimum/maximum |
Number range restriction | "minimum": 1, "maximum": 100 |
pattern |
Regex pattern for string | "pattern": "^[0-9]{9}$" |
minLength/maxLength |
String length | "minLength": 3, "maxLength": 50 |
items |
Type of elements in array | "items": {"type": "string"} |
- description is crucial - AI uses it to understand what to extract
- Be specific - provide date format, units, expected values
- Use enum when possible - this reduces errors and improves results
- Mark required - AI will try to obtain this data before calling
Webhook URL Verification
Before activating the tool, the system must verify that your endpoint is accessible and properly configured. Verification ensures that the webhook will work when the chatbot needs it.
Verification Process
- You click the button "Verify URL" in the management panel
- System generates a unique
verification_token - Sends a POST request to your endpoint:
POST https://your-endpoint.com/webhook
Content-Type: application/json
{
"verification_token": "abc123def456...",
"tool_name": "your_tool_name",
"action": "verify"
}4. Your endpoint should respond with code 200 OK and return the token:
HTTP/1.1 200 OK
Content-Type: application/json
{
"success": true,
"verification_token": "abc123def456..."
}5. System checks if the returned token matches the sent one
6. If yes - status changes to VERIFIED
Implementation Example (Python/Flask)
@app.route('/webhook', methods=['POST'])
def handle_webhook():
data = request.json
# URL Verification
if data.get('action') == 'verify':
return jsonify({
'success': True,
'verification_token': data.get('verification_token')
}), 200
# Normal tool handling
# ... your logic ...
return jsonify({'success': True, 'result': 'OK'}), 200Implementation Example (Node.js/Express)
app.post('/webhook', (req, res) => {
const data = req.body;
// URL Verification
if (data.action === 'verify') {
return res.json({
success: true,
verification_token: data.verification_token
});
}
// Normal tool handling
// ... your logic ...
res.json({ success: true, result: 'OK' });
});Common verification problems
| Problem | Cause | Solution |
|---|---|---|
| Timeout | Endpoint doesn't respond within 10 seconds | Check if URL is correct and accessible. Use curl for testing. |
| SSL Error | Invalid HTTPS certificate | Make sure the SSL certificate is valid and trusted |
| 404 Not Found | Endpoint does not exist | Check URL path - make sure route is correctly defined |
| Token mismatch | Returned token does not match | Make sure you return EXACTLY the same token you received |
- Endpoint MUST use HTTPS (not HTTP)
- Cannot be a local address (localhost, 192.168.x.x, 10.x.x.x)
- SSL certificate must be valid and trusted
- Port must be standard (443) or explicitly specified in URL
Webhook Request Format
When chatbot calls your tool, system sends HTTP POST request to your webhook endpoint. Learn the request format to properly handle calls.
Default payload structure
If you have not configured custom request template, we send standard structure:
POST https://your-endpoint.com/webhook
Content-Type: application/json
X-Signature-Timestamp: 1704034800
X-Signature-Nonce: abc123def456...
X-Signature-Hash: hmac_sha256_signature
{
"tool_name": "create_newsletter_subscription",
"arguments": {
"email": "[email protected]",
"full_name": "John Smith",
"interests": ["technology", "AI"]
},
"timestamp": "2024-01-10T15:30:00+01:00"
}Payload Fields
| Field | Type | Description |
|---|---|---|
tool_name |
string | Name of called tool |
arguments |
object | Parameters extracted by AI according to your schema |
timestamp |
string (ISO 8601) | Call timestamp |
Custom headers
You can configure custom HTTP headers in tool settings (JSON format):
{
"X-API-Key": "your-api-key",
"X-Custom-Header": "value",
"Authorization": "Bearer token123"
}Request Body Template (Advanced)
If you need custom payload format, you can use Jinja2 template.
Template has access to variable arguments containing parameters:
Example template:
{
"event": "subscription",
"data": {
"subscriber_email": "{{ arguments.email }}",
"name": "{{ arguments.full_name }}",
"tags": {{ arguments.interests | tojson }},
"source": "chatbot",
"subscribed_at": "{{ now().isoformat() }}"
}
}Resulting payload:
{
"event": "subscription",
"data": {
"subscriber_email": "[email protected]",
"name": "John Smith",
"tags": ["technology", "AI"],
"source": "chatbot",
"subscribed_at": "2024-01-10T15:30:00+01:00"
}
}- Template length is limited for security
- Rendering has timeout protecting against hanging
- Result size is limited
- Template is executed in secure environment (no access to file system and server resources)
Webhook Security (HMAC-SHA256)
HMAC (Hash-based Message Authentication Code) ensures that webhook comes from us and was not modified during transmission. If you configured webhook secret, every request is signed.
Signature headers
| Header | Description | Example |
|---|---|---|
X-Signature-Timestamp |
Unix timestamp of call (seconds) | 1704034800 |
X-Signature-Nonce |
Random identifier preventing replay attacks (32 hex chars) | a1b2c3d4e5f6... |
X-Signature-Hash |
HMAC-SHA256 signature | 7f8b9c... |
Verification Algorithm
- Read
timestamp,nonceandhashfrom headers - Check if timestamp is not older than 5 minutes (replay attack protection)
- Read raw request body (JSON string)
- Compose message:
timestamp.nonce.body - Calculate HMAC-SHA256 using your webhook secret
- Compare calculated hash with received one
Verification example (Python)
import hmac
import hashlib
import time
from flask import request, abort
WEBHOOK_SECRET = "your-webhook-secret"
MAX_TIMESTAMP_AGE = 300 # Recommended: a few minutes
@app.route('/webhook', methods=['POST'])
def handle_webhook():
# 1. Get headers
timestamp = request.headers.get('X-Signature-Timestamp')
nonce = request.headers.get('X-Signature-Nonce')
received_hash = request.headers.get('X-Signature-Hash')
if not all([timestamp, nonce, received_hash]):
abort(401, 'Missing signature headers')
# 2. Check timestamp validity
if abs(time.time() - int(timestamp)) > MAX_TIMESTAMP_AGE:
abort(401, 'Request timestamp too old')
# 3. Get raw body
body = request.get_data(as_text=True)
# 4. Compose message
message = f"{timestamp}.{nonce}.{body}"
# 5. Calculate HMAC
expected_hash = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
# 6. Compare
if not hmac.compare_digest(expected_hash, received_hash):
abort(401, 'Invalid signature')
# Signature valid - process request
data = request.json
# ... your logic ...
return {'success': True, 'result': 'OK'}Verification example (Node.js)
const crypto = require('crypto');
const WEBHOOK_SECRET = 'your-webhook-secret';
const MAX_TIMESTAMP_AGE = 300; // 5 minutes
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
// 1. Get headers
const timestamp = req.headers['x-signature-timestamp'];
const nonce = req.headers['x-signature-nonce'];
const receivedHash = req.headers['x-signature-hash'];
if (!timestamp || !nonce || !receivedHash) {
return res.status(401).json({error: 'Missing signature headers'});
}
// 2. Check validity
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > MAX_TIMESTAMP_AGE) {
return res.status(401).json({error: 'Request too old'});
}
// 3. Get raw body (as Buffer/string)
const body = req.body.toString('utf8');
// 4. Compose message
const message = `${timestamp}.${nonce}.${body}`;
// 5. Calculate HMAC
const expectedHash = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(message)
.digest('hex');
// 6. Compare (timing-safe)
if (!crypto.timingSafeEqual(
Buffer.from(expectedHash),
Buffer.from(receivedHash)
)) {
return res.status(401).json({error: 'Invalid signature'});
}
// Signature valid - process request
const data = JSON.parse(body);
// ... your logic ...
res.json({success: true, result: 'OK'});
});Verification example (PHP)
<?php
$webhookSecret = 'your-webhook-secret';
$maxTimestampAge = 300; // 5 minutes
// 1. Get headers
$timestamp = $_SERVER['HTTP_X_SIGNATURE_TIMESTAMP'] ?? '';
$nonce = $_SERVER['HTTP_X_SIGNATURE_NONCE'] ?? '';
$receivedHash = $_SERVER['HTTP_X_SIGNATURE_HASH'] ?? '';
if (!$timestamp || !$nonce || !$receivedHash) {
http_response_code(401);
die(json_encode(['error' => 'Missing signature headers']));
}
// 2. Check validity
if (abs(time() - intval($timestamp)) > $maxTimestampAge) {
http_response_code(401);
die(json_encode(['error' => 'Request too old']));
}
// 3. Get raw body
$body = file_get_contents('php://input');
// 4. Compose message
$message = "{$timestamp}.{$nonce}.{$body}";
// 5. Calculate HMAC
$expectedHash = hash_hmac('sha256', $message, $webhookSecret);
// 6. Compare (timing-safe)
if (!hash_equals($expectedHash, $receivedHash)) {
http_response_code(401);
die(json_encode(['error' => 'Invalid signature']));
}
// Signature valid - process request
$data = json_decode($body, true);
// ... your logic ...
echo json_encode(['success' => true, 'result' => 'OK']);
?>- Always verify signature if you use webhook secret!
- Check timestamp - reject old requests (recommended: a few minutes)
- Track nonce - store used nonces in cache to prevent reuse (replay attack)
- Use timing-safe compare - avoid timing attacks when comparing hashes
- Protect webhook secret - don't commit to repo, use environment variables or secret manager
Webhook Response Format
Your endpoint must return JSON response that chatbot will use to continue conversation. Response format is flexible - you can return any structure.
Minimal response (success)
HTTP/1.1 200 OK
Content-Type: application/json
{
"success": true,
"result": "User has been added to newsletter!"
}Error response
HTTP/1.1 200 OK
Content-Type: application/json
{
"success": false,
"error": "This email address is already subscribed to newsletter"
}Rich response with additional data
{
"success": true,
"result": "Reservation confirmed",
"data": {
"reservation_id": "RES-12345",
"date": "2024-01-15",
"time": "18:00",
"guests": 4,
"table_number": "A5",
"confirmation_code": "CONF789"
},
"message": "Your reservation has been confirmed. Confirmation number: CONF789"
}Technical Requirements
| Requirement | Value | Notes |
|---|---|---|
| HTTP status code | 200 OK |
Other codes (4xx, 5xx) treated as error |
| Content-Type | application/json |
Required |
| Maximum size | Limited | Too large responses will be rejected |
| Timeout | Configurable (1-30s) | Default 10 seconds |
Response Mapping (advanced)
If your API returns complex structure, you can configure response mapping to extract needed fields:
Example - Your API returns:
{
"status": "ok",
"data": {
"user": {
"id": 123,
"email": "[email protected]"
},
"subscription": {
"active": true,
"created_at": "2024-01-10T15:30:00Z"
}
},
"meta": {
"request_id": "req-abc"
}
}Configure mapping (JSON):
{
"user_id": "data.user.id",
"email": "data.user.email",
"is_active": "data.subscription.active",
"request_id": "meta.request_id"
}Chatbot will receive:
{
"success": true,
"user_id": 123,
"email": "[email protected]",
"is_active": true,
"request_id": "req-abc"
}Execution Mode
Custom tools execute synchronously (SYNC). System waits for webhook response before continuing conversation, so chatbot receives result and can use it in response to user.
Synchronous mode characteristics
- Chatbot waits for response from webhook before continuing conversation
- Maximum timeout: 30 seconds (configurable in range 1-30s, default 10s)
- Automatic retry on errors - up to 3 attempts with exponential backoff
- Full logging request/response for each call
- Webhook response is passed to AI as function result
Timeout configuration
In advanced tool settings you can adjust timeout to your needs:
- Short timeout (1-5s): Ideal for fast database operations, cache checking
- Medium timeout (5-15s): Standard API calls, saving to CRM
- Long timeout (15-30s): Complex operations, integrations with slower systems
Retry policy
System automatically retries failed calls according to configuration:
| Attempt | Delay | Description |
|---|---|---|
| 1 | 0s | First call - immediate |
| 2 | 1s | First retry |
| 3 | 2s | Second retry |
| 4 | 4s | Third retry (last) |
Example Use Cases
- Product Availability Check - chatbot waits for result and informs user about inventory status
- Creating leads in CRM - confirmation of contact saved
- Appointment booking - availability verification and reservation confirmation
- Data Retrieval from database - searching information and presenting results
- Integrations with external APIs - getting order status, tracking shipment
Advanced Features
Custom Tools offer a range of advanced features allowing precise customization of tool behavior to your needs.
Rate Limiting
Rate limiting protects your endpoint from overload and ensures fair resource utilization. Each tool has configured call limits that you can adjust to your needs.
How does it work?
- System tracks number of tool calls in specified time period
- When limit is exceeded, subsequent calls are rejected
- Chatbot receives information about limit exceeded and can inform user
- Counter resets automatically after period elapses
Retry Logic
System automatically retries failed webhook calls to increase reliability. Retry is applied in the following cases:
- Timeout - endpoint did not respond in specified time
- Network errors - connection issues, DNS, SSL
- 5xx errors - server errors (500, 502, 503, 504)
- Connection errors - connection refused, reset
Retry is NOT applied for:
- 4xx errors (400, 401, 404 etc.) - client errors require fixing
- 200 responses with {"success": false} - logical application error
Exponential Backoff Strategy
Delay between attempts grows exponentially, giving system time to recover and reduces load during failure.
Fallback Response
Fallback response is the response that chatbot will receive when webhook call fails (after exhausting retries). This allows chatbot to continue conversation with user in a graceful way, instead of showing error.
Example fallback response (JSON):
{
"success": false,
"message": "Sorry, we could not process your request at this time. Try again in a moment or contact us by email: [email protected]"
}Chatbot will use this message to inform user about problem in natural way.
- Explain problem in simple way (without technical details)
- Suggest alternative solution (email, phone, try later)
- Apologize for inconvenience
- Be specific and helpful
Custom Request Templates (Jinja2)
If your API requires specific payload format, you can use Jinja2 template for full control over request structure.
Available variables in template:
| Variable | Type | Description |
|---|---|---|
arguments |
dict | Call parameters extracted by AI |
tool_name |
string | Name of called tool |
now() |
function | Returns current datetime |
Advanced example - CRM integration:
{
"action": "create_contact",
"timestamp": "{{ now().isoformat() }}",
"source": {
"channel": "chatbot",
"tool": "{{ tool_name }}"
},
"contact": {
"email": "{{ arguments.email }}",
"firstName": "{{ arguments.first_name | default('') }}",
"lastName": "{{ arguments.last_name | default('') }}",
"phone": "{{ arguments.phone | default('') }}",
"tags": {{ arguments.interests | default([]) | tojson }},
"customFields": {
"leadSource": "chatbot_{{ tool_name }}",
"capturedAt": "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
}
},
"metadata": {
"apiVersion": "v2"
}
}Available Jinja2 filters:
default(value)- default value if variable does not existupper,lower- change letter casetojson- conversion to JSONlength- length of list/stringtrim- remove white spaces
Response Mapping
Response mapping allows extracting specific fields from webhook response using dot notation.
Example - API returns nested structure:
{
"status": "success",
"code": 201,
"payload": {
"reservation": {
"id": "RES-789",
"confirmationCode": "ABC123",
"guestDetails": {
"name": "John Smith",
"phone": "+48500600700"
}
},
"timestamp": "2024-01-10T15:30:00Z"
}
}Mapping (JSON):
{
"reservation_id": "payload.reservation.id",
"confirmation": "payload.reservation.confirmationCode",
"guest_name": "payload.reservation.guestDetails.name",
"created_at": "payload.timestamp"
}Chatbot will receive simplified structure:
{
"success": true,
"reservation_id": "RES-789",
"confirmation": "ABC123",
"guest_name": "John Smith",
"created_at": "2024-01-10T15:30:00Z"
}Testing and Debugging
Effective testing and debugging are crucial for ensuring Custom Tools reliability. System offers range of tools for diagnosing and solving problems.
Test Function in panel
In the management panel each tool has a button "Test", which allows for quick webhook testing without needing full conversation.
How to test tool:
- Go to tool details in Custom Tools panel
- Click the button "Test Tool"
- Enter example parameter values in JSON format
- Click "Execute test"
- System will send webhook to your endpoint and display:
- Sent request (headers + body)
- Received response (status, headers, body)
- Execution time
- Possible errors
Example test payload:
{
"email": "[email protected]",
"full_name": "John Test",
"interests": ["AI", "marketing"]
}Execution History
Every Custom Tool call is logged with full details. Execution history is your main tool for analysis and debugging.
What is logged?
| Information | Description |
|---|---|
| Timestamp | Exact date and time of call |
| Tool name | Name of called tool |
| Arguments | Parameters extracted by AI |
| Request payload | Full payload sent to webhook |
| Request headers | All HTTP headers (including HMAC) |
| Response status | HTTP status code (200, 404, 500, etc.) |
| Response body | Response returned by endpoint |
| Execution time | Execution time in milliseconds |
| Status | Success, Failed, Timeout, Rate Limited |
| Error details | Error details if occurred |
| Retry attempts | Number of retries |
Filtering and Searching
In execution history you can filter by:
- Date range - last 24h, 7 days, 30 days, custom
- Status - only successful, only errors, timeouts
- Tool - select specific tool
- Text - search in payloads and responses
Analytics and Metrics
The analytics panel provides aggregated statistics to help with optimization and monitoring:
Available metrics:
- Number of calls - total, last 24h, trend
- Success rate - percentage of successful calls
- Average response time - endpoint performance
- Error rate - percentage of errors by type
- Rate limit hits - how many times the limit was exceeded
- Top errors - most common error messages
- Time chart - visualization of calls and errors
Local Debugging
During development you may need to test webhook locally. Here are best methods:
Method 1: Tunnels (ngrok, LocalTunnel, Cloudflare Tunnel)
Tunnels expose your local server to internet through temporary HTTPS URL:
# Ngrok ngrok http 3000 # Will generate URL: https://abc123.ngrok.io # Use this URL in Custom Tool configuration
Method 2: Webhook testing services (webhook.site, requestbin)
These services give you temporary URL to which you can send webhooks and browse them in browser. Great for initial testing without writing code.
Method 3: Endpoint-side Logging
Implement detailed logging in your endpoint:
@app.route('/webhook', methods=['POST'])
def handle_webhook():
# Log everything
logger.info(f"Received webhook: {request.method} {request.url}")
logger.info(f"Headers: {dict(request.headers)}")
logger.info(f"Body: {request.get_data(as_text=True)}")
try:
result = process_webhook(request.json)
logger.info(f"Success: {result}")
return jsonify(result)
except Exception as e:
logger.error(f"Error: {str(e)}", exc_info=True)
return jsonify({"error": str(e)}), 500Pre-production Testing Checklist
- Tested all parameter scenarios (required, optional, edge cases)
- Webhook secret configured and verification works
- Endpoint responds in < 10 seconds (ideally < 3 sec)
- Error handling returns meaningful messages
- HTTPS certificate is valid and trusted
- Rate limiting adjusted to expected traffic
- Monitoring and alerts configured (Sentry, CloudWatch, etc.)
- Fallback response configured and tested
- Endpoint is idempotent (retry-safe)
- Tested with a real chatbot in conversation
Best Practices
The following best practices will help you create reliable, secure and efficient Custom Tools.
🔒 Security
1. Always use Webhook Secret in production
# Generate a strong, random secret (min. 32 characters) import secrets webhook_secret = secrets.token_urlsafe(32) # E.g.: "whsec_abc123XYZ..."
2. Verify HMAC signature on every request
- Check timestamp - reject old requests
- Track nonce in cache to prevent replay attacks
- Use timing-safe compare when comparing hashes
3. Limit access to endpoint
- Use firewall rules if possible (whitelist IP)
- Implement rate limiting on your server
- Log all failed verification attempts
4. Don't reveal sensitive details in responses
# ❌ Bad - reveals details
{"error": "SQL error at line 42: table 'users' doesn't exist"}
# ✅ Good - general message
{"success": false, "message": "Could not process request"}5. Store webhook secret securely
- Use environment variables or secret manager
- Don't commit secrets to repository
- Rotate secrets regularly (every 3-6 months)
- Use different secrets for dev/staging/production
⚡ Performance and reliability
1. Optimize response time
- Goal: < 3 seconds for 95% of requests
- Use database indexes for frequently queried columns
- Cache repeatable queries (Redis, Memcached)
- Consider async processing for long-running operations
2. Endpoint must be idempotent
# Use unique identifiers for deduplication
@app.route('/webhook', methods=['POST'])
def handle_webhook():
data = request.json
request_id = data.get('request_id') # Unique ID from payload
# Check if you already processed this request
if cache.exists(f"processed:{request_id}"):
return cached_response
# Process...
result = process_action(data)
# Save in cache (TTL = 1 hour)
cache.setex(f"processed:{request_id}", 3600, result)
return result3. Graceful error handling
- Always return HTTP 200 for application errors
- Use
{"success": false, "error": "..."}for logical errors - Return 5xx only for real server errors
- Implement fallback behaviors
4. Monitor and Alert
# Example Sentry integration
import sentry_sdk
@app.route('/webhook', methods=['POST'])
def handle_webhook():
try:
return process_webhook(request.json)
except Exception as e:
sentry_sdk.capture_exception(e)
return {"success": false, "message": "Internal error"}, 500📝 Schema and description design
1. Write clear, specific tool descriptions
# ❌ Bad - too general "Manages reservations" # ✅ Good - specific, with examples "Creates table reservation in restaurant. Use when user wants to reserve a table, schedule a visit or reserve a seat. Requires date, time and number of people."
2. Define precise parameters
- Use
enumwhen possible (reduces errors) - Provide examples in
description - Specify formats (YYYY-MM-DD, HH:MM, email, etc.)
- Mark required fields in
required
{
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "Reservation date in YYYY-MM-DD format, e.g. 2024-01-15",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "urgent"],
"description": "Ticket priority"
}
},
"required": ["date", "priority"]
}3. Avoid too many parameters
- Ideal: 3-5 parameters
- Max: 10 parameters
- If you need more - consider splitting into multiple tools
🎯 User Experience
1. Messages for users
- Write clearly and understandably (avoid technical jargon)
- Be positive and helpful
- Provide next steps or alternatives
- Personalize when possible
# ❌ Technically correct, but not user-friendly
{"result": "Record inserted with ID 12345"}
# ✅ User-friendly
{"result": "Great! I've added you to our newsletter. You'll receive our updates at [email protected]"}2. Fallback responses should be helpful
{
"success": false,
"message": "Sorry, our reservations are currently unavailable. You can call us at: +48 123 456 789 or try again in a few minutes."
}🏗️ Architecture and Organization
1. One tool = one responsibility
- Avoid universal tools like "manage_everything"
- Better:
create_lead,update_lead,get_lead_status - AI better understands specific, specialized tools
2. Name tools clearly
# ❌ Bad "tool1", "webhook_handler", "api_call" # ✅ Good "create_newsletter_subscription" "check_product_availability" "book_appointment"
3. Version your API
- Add version to URL:
/api/v1/webhook - Or in custom headers:
X-API-Version: 1 - Maintain backwards compatibility when possible
4. Log everything (in dev and prod)
- Request ID for correlation
- Input parameters (without sensitive data!)
- Execution time
- Errors with stack traces
- Use structured logging (JSON)
📊 Testing and Deployment
1. Test in staging before production
- Use separate tools for dev/staging/prod
- Test with real data (but anonymized)
- Verify all edge cases
- Load testing at expected traffic + 50%
2. Deployment checklist
- ✓ HTTPS certificate valid and automatically renewed
- ✓ Webhook secret configured
- ✓ Rate limiting set up
- ✓ Monitoring and alerts ready
- ✓ Backup strategy for data
- ✓ Rollback plan prepared
- ✓ Documentation for team
Troubleshooting
Encountered a problem with Custom Tools? Below you will find solutions to the most common issues.
❌ Problem: URL verification fails
Symptom:
Tool status is FAILED after verification attempt
Possible causes and solutions:
| Cause | Solution |
|---|---|
| Endpoint doesn't respond |
• Check if server is running (curl https://your-url.com)• Check server logs • Make sure the port is open in firewall |
| SSL/TLS error |
• Check certificate: openssl s_client -connect your-domain.com:443• Make sure the certificate is valid and trusted • Check if the full certificate chain is installed |
| Timeout |
• Endpoint must respond within a few seconds • Make sure verification does not trigger heavy operations • Check network latency |
| 404 Not Found |
• Check URL path - is routing correctly configured? • Test locally: curl -X POST https://your-url.com/webhook -d '{}'
|
| Token mismatch |
• Make sure you return EXACTLY the same token you received • Check that you are not modifying the token (trim, encode, etc.) |
❌ Problem: Tool is not called by chatbot
Symptom:
Chatbot ignores tool, even though it should call it
Possible causes and solutions:
-
Description is too general or unclear
→ Rewrite description using specific examples and usage scenarios
→ Add keywords that user might use in conversation -
Tool is inactive
→ Check status - must be ACTIVE
→ If status is DRAFT or INACTIVE - activate tool -
Parameters are too complex or unclear
→ Simplify schema - AI may have problem extracting too many parameters
→ Add clear descriptions to each parameter -
Conflict with other tools
→ If you have several similar tools, AI may choose wrong one
→ Differentiate descriptions to be unambiguous
❌ Problem: Webhook timeout
Symptom:
Execution history shows status "Timeout", endpoint didn't respond in time
Solutions:
- Increase timeout in tool settings (max 30s)
- Optimize endpoint:
- Add database indexes
- Use cache for repeatable queries
- Profile code to find bottlenecks
- Consider async processing:
- Return immediate response ("Processing...")
- Execute heavy operation in background (Celery, RQ, etc.)
- Notify user through another channel (email, SMS)
❌ Problem: HMAC signature invalid
Symptom:
Endpoint rejects requests with error "Invalid signature"
Debugging checklist:
- Check if you're using correct secret
# Print secret used in code (only in dev!) print(f"Secret: {WEBHOOK_SECRET}") # Compare with the secret in Custom Tools panel - Check message assembly order
# Must be: timestamp.nonce.body message = f"{timestamp}.{nonce}.{body}" # NOT: nonce.timestamp.body (wrong!) # NOT: timestamp-nonce-body (separator must be a dot!) - Check if you're using raw body
# ✅ Correct - raw string body = request.get_data(as_text=True) # ❌ Wrong - parsed JSON body = json.dumps(request.json) # This will change key order!
- Check hash algorithm
# Must be HMAC-SHA256 hash = hmac.new(secret, message, hashlib.sha256).hexdigest() # Encoding: UTF-8 for secret and message
- Compare hashes securely
# Python hmac.compare_digest(expected, received) # Node.js crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received)) # PHP hash_equals($expected, $received)
❌ Problem: Rate limit exceeded
Symptom:
Execution history shows "Rate Limited", subsequent calls are blocked
Solutions:
- Increase limits in tool settings if justified
- Split functionality - maybe one tool does too much?
- Implement caching - if many calls return same data
- Educate users - maybe they're trying to spam?
❌ Problem: AI extracts incorrect parameters
Symptom:
Webhook receives incorrect parameter values or missing data
Solutions:
- Fix parameter descriptions
# ❌ Bad - too general "date": {"type": "string", "description": "Date"} # ✅ Good - specific with example "date": { "type": "string", "description": "Appointment date in YYYY-MM-DD format, e.g., 2024-01-15", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" } - Use enum for limited set of values
"priority": { "type": "string", "enum": ["low", "normal", "high", "urgent"], "description": "Ticket priority level" } - Add validation constraints
minimum,maximumfor numbersminLength,maxLengthfor stringspatternfor regex validation
- Test with different phrasings - see how AI interprets different phrases
❌ Problem: Endpoint returns data but chatbot doesn't use it
Symptom:
Webhook works and returns response, but chatbot doesn't use data in conversation
Solutions:
- Check response format
# Make sure you're returning JSON Content-Type: application/json {"success": true, "result": "Data to pass to the chatbot"} - Make sure success = true for successful operations
- Use response mapping if you return a complex structure
- Add descriptive fields that AI can use in the response
# Instead of just IDs: {"order_id": 12345} # Add context: { "success": true, "order_id": "ORD-12345", "confirmation_message": "Your order has been accepted. Number: ORD-12345", "delivery_date": "2024-01-20" }
🆘 Further Steps if the Problem Persists
- Check Execution History - full request/response logs can reveal the problem
- Test locally - using ngrok/webhook.site to see exactly what is being sent
- Compare with working examples - see how they are configured in the documentation
- Contact support - include the execution ID and problem details
📚 Useful Debugging Tools
- webhook.site - temporary endpoint for viewing webhooks
- ngrok - tunnel for testing localhost
- Postman/Insomnia - testing API requests
- JSON Schema Validator - validating parameters schema
- SSL Labs - checking SSL/TLS certificate
- cURL - testing endpoint from command line