webhook_tools
Webhook Tools Development Guide
This document covers creating and registering webhook tools for ElevenLabs integration.
Overview
CRITICAL: When creating new webhook tools, you must register them in BOTH:
- Database - Enables toggle in our UI (
webhook_tools,agent_webhook_config) - ElevenLabs - Makes tool available to agents during conversations
Common Mistake: Toggling a tool ON in our UI does NOT automatically add it to ElevenLabs. The agent won’t be able to use the tool until it’s registered in ElevenLabs workspace.
Two-Step Process
Step 1: Add Tool to Database
IMPORTANT: Always include elevenlabs_tool_id and organization_id so we know which ElevenLabs account the tool belongs to.
INSERT INTO webhook_tools (tool_name, elevenlabs_tool_id, organization_id, display_name, description, webhook_url, is_system_tool)
VALUES ('my_tool', 'tool_xxxxx', 2, 'My Tool', 'Description of what it does...', 'https://api.triluna.app/api/my-tool/webhook', 1);
| Column | Description |
|---|---|
elevenlabs_tool_id | The tool_xxx ID from ElevenLabs workspace (visible at https://elevenlabs.io/app/agents/tools) |
organization_id | Our org ID — determines which ElevenLabs API key to use (e.g., 7 = MyJava, 2 = TriLuna platform) |
Step 2: Create ElevenLabs Registration Script
Check existing scripts for reference:
ls webapp/server/scripts/create-*-tool.js
Reference Scripts:
create-realtor-listings-tool.js- Pattern examplecreate-google-sheets-tool.js- Multi-tenant toolcreate-email-tool.js- Email integration
Script Template
const axios = require('axios');
require('dotenv').config({ path: require('path').join(__dirname, '../.env') });
const ELEVENLABS_API_KEY = process.env.ELEVENLABS_API_KEY;
async function createMyTool() {
const toolPayload = {
tool_config: {
type: "webhook",
name: "my_tool_name",
description: "What this tool does...",
response_timeout_secs: 20,
disable_interruptions: false,
force_pre_tool_speech: false,
assignments: [],
api_schema: {
url: "https://api.triluna.app/api/my-tool/webhook",
method: "POST",
path_params_schema: {},
query_params_schema: null,
request_body_schema: {
type: "object",
required: ["agent_id", "action"],
properties: {
agent_id: {
type: "string",
enum: null,
dynamic_variable: "system__agent_id",
constant_value: ""
},
conversation_id: {
type: "string",
enum: null,
dynamic_variable: "system__conversation_id",
constant_value: ""
},
action: {
type: "string",
description: "Action to perform",
enum: ["action1", "action2"],
dynamic_variable: "",
constant_value: ""
}
// Add more parameters as needed
}
},
request_headers: {
"Content-Type": "application/json"
},
auth_connection: null
},
dynamic_variables: {
dynamic_variable_placeholders: {}
}
}
};
try {
const response = await axios.post(
'https://api.elevenlabs.io/v1/convai/tools',
toolPayload,
{
headers: {
'xi-api-key': ELEVENLABS_API_KEY,
'Content-Type': 'application/json'
}
}
);
console.log('Tool created:', response.data);
return response.data;
} catch (error) {
if (error.response?.status === 409) {
console.log('Tool already exists, fetching existing...');
// Handle 409 conflict - tool already exists
}
throw error;
}
}
createMyTool().catch(console.error);
Step 3: Run Script and Add to Agent
cd webapp/server/scripts
node create-my-tool.js
# Tool is now available at: https://elevenlabs.io/app/agents/tools
# Add it to specific agents through ElevenLabs UI
Schema Requirements
| Requirement | Details |
|---|---|
Wrap in tool_config | Root object must be { tool_config: { ... } } |
| Dynamic variables | Properties with dynamic_variable cannot have description |
| System variables | Use system__agent_id and system__conversation_id |
force_pre_tool_speech | Must be boolean (not “auto”) |
| Handle 409 | Tool may already exist - fetch existing tool info |
Critical Database Schema
IMPORTANT: When querying elevenlabs_agents for user/owner information:
// WRONG - These columns DO NOT EXIST
SELECT user_id, owner_user_id FROM elevenlabs_agents WHERE agent_id = ?
// CORRECT - Use primary_owner_id
SELECT primary_owner_id FROM elevenlabs_agents WHERE agent_id = ?
const userId = agents[0].primary_owner_id;
Common Pattern for Webhook Tools:
// Get agent owner
const [agents] = await database.query(
'SELECT agent_id, primary_owner_id FROM elevenlabs_agents WHERE agent_id = ?',
[params.agent_id]
);
if (agents.length === 0) {
return res.status(404).json({ error: 'Agent not found' });
}
const userId = agents[0].primary_owner_id;
// Now use userId for user-specific queries
const credentials = await getCredentials(userId, params.agent_id);
Debugging Webhook Tools
Agent can’t use the tool during conversation:
- Check if tool exists in ElevenLabs: https://elevenlabs.io/app/agents/tools
- Check if tool is added to the specific agent
- Check webhook logs table for errors:
SELECT id, agent_id, email_action, status, error_message, created_at
FROM webhook_logs
WHERE agent_id = 'agent_xxx'
ORDER BY created_at DESC
LIMIT 10;
Common Issues:
| Issue | Cause | Solution |
|---|---|---|
| Agent can’t access tool | Tool toggled ON in UI but not registered in ElevenLabs | Run registration script |
| Tool not available | Tool registered but not added to agent | Add via ElevenLabs UI |
| 404 errors | Route not defined | Check server routes, restart server |
| ”Unknown column” errors | Using wrong column name | Use primary_owner_id not user_id |
Last Updated: 2026-01-14