Webhook API Documentation

Neuralai AI calls integration via webhook

SIP Trunk Configuration (BYOT)

Bring Your Own Telephony — Use your personal SIP trunk

What is BYOT

Neuralai is a SaaS provider: it supplies the AI and platform, but does NOT provide phone lines. The sender number (Caller ID) used for calls comes from the SIP trunk you configure. Without an active SIP trunk, no calls can be made (error 'no_sip_trunk').

Supported SIP Providers

twilio
Twilio

Elastic SIP Trunking. Global coverage, ideal for international campaigns.

vonage
Vonage

SIP Trunking. Reliable, good European coverage.

telnyx
Telnyx

SIP Trunking. High quality, low latency, great for high volume.

generic_sip
Generic SIP

Any standard SIP provider. Requires URI, port, protocol and credentials.

Configuration Fields

trunk_nameDescriptive name (e.g. 'Twilio Production')
provider_typetwilio | vonage | telnyx | generic_sip
sip_uriSIP server address (e.g. sip.provider.com)
sip_portSIP port (default 5060, 5061 for TLS)
transport_protocolUDP | TCP | TLS (TLS recommended)
usernameSIP authentication username
encrypted_passwordSIP password (AES-256-GCM encrypted)
from_numberE.164 Caller ID number (e.g. +393331234567)

Caller ID and Destination Country Requirement

The sender number (Caller ID) MUST match the country of the recipients. A non-matching number is blocked by providers under national telecom regulators' anti-spoofing rules (e.g. AGCOM for Italy, FCC for the USA, Ofcom for the UK, ARCEP for France, BNetzA for Germany).

🇮🇹 +39

Italian number

🇩🇪 +49

German number

🇫🇷 +33

French number

Security and Encryption

  • ✓SIP passwords encrypted with AES-256-GCM in the database
  • ✓No plaintext access to sensitive data
  • ✓Only one active SIP trunk per user at a time
  • ✓TLS protocol recommended for greater security

Public Documentation Link

Share this link with AI assistants (Vibe Coding, Cursor, Copilot) to give them full context of the webhook API.

https://neuralai.pro/webhook-code-docs

💡 Tip: paste this link in your AI coding assistant's prompt to instruct it how to integrate Neuralai's AI webhook calls.

How to use the webhook

Send a POST request to the webhook URL with the phone number in E.164 format. Config parameters (voice, language, KB, etc.) are used as defaults, but you can override them in the payload.

Mandatory KYC verification

To use AI Webhook Calls, you must complete KYC verification (identity, company, and biometric selfie). Verification is shared across all outbound services.

•KYC tab in the Webhook Calls dashboard (same component as Marketing Outbound AI)
•KYC data is synchronized: changes in one section reflect in all others
•KYC data deletion (GDPR Art. 17): immediately blocks all outbound services

Payload fields

to_numberREQUIREDE.164 phone number
recipient_nameoptionalRecipient name (if absent, AI asks for name or greets without one)
topicoptionalTopic (override)
caller_on_behalf_ofoptionalOn behalf of (override)
agent_nameoptionalAI agent name (override). Empty = no personal name
custom_kb_idoptionalKB ID (override)
knowledge_modesoptionalModes array (override)
agent_instructionsoptionalAgent behavioral rules (override)
voice_typeoptional"female" | "male"
languageoptionalLanguage (override)
timezoneoptionalIANA timezone (override). E.g. Europe/Rome, America/New_York, Asia/Tokyo. Controls the agent's Current Time Awareness for time-appropriate greetings.
topic_labeloptional⚠️ Config-only (not payload). Widget dropdown label

AI agent name (agent_name)

The agent_name field lets you assign a personal name to the AI agent (e.g. "Lucy", "Marco"). If provided, the agent introduces itself with that name during the call. If empty or omitted, the agent does NOT use a personal name: it introduces itself only as "an assistant from Neuralai" calling on behalf of the configured sender. This ensures the agent never invents a name on its own.

With agent name

"agent_name": "Lucy"

Agent says: "Hello, I'm Lucy, calling on behalf of..."

Without name (empty)

"agent_name": ""
// oppure: omesso

Agent says: "Hello, I'm calling on behalf of..." (no invented name)

Priority: payload > webhook config > empty. The name can be set as default in the webhook config and overridden per-call via payload.

Agent behavioral instructions (agent_instructions)

The agent_instructions field lets you send mandatory behavioral rules the AI agent must follow during the call. If omitted in the payload, the value configured in the webhook is used. Instructions are injected into the agent prompt as binding rules.

Usage examples

  • Never ask for sensitive data (credit cards, tax codes, passwords) by phone
  • If you cannot resolve an issue, invite the user to open a ticket
  • Maintain a friendly and professional tone at all times

KB API (Knowledge Base API Hub)

The kb_api mode lets the AI agent query your API Hub Knowledge Bases during the call via a voice provider custom function. Token charge like buildKnowledgeBase (15-30 tokens per query).

  • Config-only: select API Hub KBs from the webhook config (not an HTTP payload field).
  • Payload: include "kb_api" in the knowledge_modes array to activate the mode in the call.
  • Exclusions: kb_api is incompatible with topic_only, basic_knowledge, web_search_unlimited, and custom_kb.
{
  "to_number": "+393331234567",
  "topic": "Supporto tecnico",
  "knowledge_modes": ["topic_only", "kb_api"]
}

Transfer to human operator

When enabled in the webhook config, the AI agent can transfer the call to a human operator if the recipient explicitly requests it. These are config fields (not HTTP payload): transfer_to_human_enabled, transfer_to_human_number, transfer_sip_method.

INVITEThe voice provider bridges: minute billing continues until the call ends, including the operator conversation. Always supported.
REFERThe provider starts a separate call: billing stops at the end of the AI portion; the operator conversation is not billed by the wallet. Requires a REFER-capable trunk.

Config example (manageWebhookCallConfig API):

{
  "transfer_to_human_enabled": true,
  "transfer_to_human_number": "+393331234567",
  "transfer_sip_method": "sip_invite"
}
Minimal payload
{
  "to_number": "+393331234567"
}

Only the phone number is required. All other parameters use the webhook configuration defaults.

Full payload (all fields)
{
  "to_number": "+393331234567",
  "recipient_name": "Mario Rossi",
  "topic": "Conferma appuntamento",
  "caller_on_behalf_of": "Studio Medico XYZ",
  "agent_name": "Lucy",
  "agent_instructions": "Non chiedere dati sensibili per telefono. Se non riesci a risolvere, invita ad aprire un ticket.",
  "custom_kb_id": "",
  "knowledge_modes": ["topic_only"],
  "voice_type": "female",
  "language": "Italian (Italiano)",
  "timezone": "Europe/Rome"
}

All optional fields override the webhook configuration defaults for this single call.

Response fields (success)

successboolean
true if the call was triggered successfully
messagestring
Human-readable status message
call_idstring
Retell AI call ID (present only on success)
outbound_call_idstring (UUID)
Internal UUID of the call record in the database
recipient_numberstring
E.164 number that was called
timestampstring (ISO 8601)
Date/time the call was triggered (UTC)
Example 200 OK response
{
  "success": true,
  "message": "Call triggered successfully",
  "call_id": "call_abc123def456...",
  "outbound_call_id": "a1b2c3d4-e5f6-...",
  "recipient_number": "+393331234567",
  "timestamp": "2026-07-26T14:30:00.000Z"
}

Error payloads

Errors always return success: false with an error field identifying the issue and a human-readable message. The HTTP status code varies by error type.

400missing_to_number

The to_number field is missing or empty in the payload.

{
  "success": false,
  "error": "missing_to_number",
  "message": "Phone number is required"
}
400invalid_phone_format

The number is not in E.164 format (must start with + and country code).

{
  "success": false,
  "error": "invalid_phone_format",
  "message": "Phone number must be in E.164 format (e.g. +393331234567)"
}
403kyc_not_verified

The user has not completed KYC verification. Required for all outbound services.

{
  "success": false,
  "error": "kyc_not_verified",
  "message": "KYC verification required to use webhook calls"
}
403no_sip_trunk

No active SIP trunk configured. Calls cannot be placed without a sender number.

{
  "success": false,
  "error": "no_sip_trunk",
  "message": "No active SIP trunk configured"
}
403webhook_inactive

The webhook configuration has been deactivated. Reactivate it from the dashboard.

{
  "success": false,
  "error": "webhook_inactive",
  "message": "This webhook configuration is not active"
}
429rate_limit_exceeded

The per-minute call limit for this webhook has been exceeded. Wait before retrying.

{
  "success": false,
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Please wait before retrying.",
  "retry_after_seconds": 30
}

Topic label (topic_label)

The topic_label field is a human-readable label (e.g. "Tech Support", "Sales") shown in the widget dropdown when topic selection is enabled. It lets your website visitors choose a department or topic, and automatically routes the call to the corresponding specialized webhook — each with its own AI agent, Knowledge Base and behavioral instructions.

1Open the webhook configuration and fill in the "Topic label (widget)" field (e.g. "Tech Support")
2Create a webhook configuration for each department/topic, each with its own agent, KB and instructions
3Go to the "Widget" tab, enable "Topic selection" and select which webhooks to include in the dropdown
4The main webhook selected at the top always appears as the first item in the dropdown (non-removable)
5Embed the script in your site: the widget automatically loads labels and routing from the server

How routing works

Each webhook has its own unique token → independent endpoint with dedicated agent, KB and instructions
The visitor selects a topic from the dropdown → the widget replaces the token in the URL with the specialized webhook's token
The call is routed to the selected webhook → the AI agent uses that webhook's topic, KB and instructions

Security badges

Green shield

Authorized domains configured: only approved domains can trigger calls.

Red shield

No domains configured: anyone can trigger calls at your expense. Configure authorized domains!

Code: config structure and embed

The widget automatically loads the configuration (including topic labels) from the server on page load. The embed script is a single line:

<script src="https://app.neuralaiweb.com/functions/serveWebhookWidget"
        data-token="YOUR_WEBHOOK_TOKEN"></script>

The server responds with a JSON object that includes the widget configuration and the topics array (with labels and security status):

{
  "topic_selection": {
    "enabled": true,
    "placeholder": "Select department...",
    "webhook_tokens": ["token_a", "token_b"]
  },
  "topics": [
    {
      "token": "MAIN_TOKEN",
      "label": "Reception",
      "secured": true
    },
    {
      "token": "token_a",
      "label": "Tech Support",
      "secured": true
    },
    {
      "token": "token_b",
      "label": "Sales",
      "secured": false
    }
  ]
}

The topic_label field is set in the webhook configuration ("Webhook" tab → edit → "Topic label"). It is not an HTTP payload field: it's configuration metadata used by the widget for the dropdown. Routing happens by replacing the token in the webhook URL.

cURL — Linux / macOS / Git Bash
curl -X POST 'https://neuralai.pro/functions/triggerWebhookCall?token=YOUR_WEBHOOK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "to_number": "+393331234567",
    "recipient_name": "Mario Rossi",
    "topic": "Conferma appuntamento",
    "caller_on_behalf_of": "Studio Medico XYZ",
    "agent_name": "Lucy",
    "agent_instructions": "Non chiedere dati sensibili per telefono. Se non riesci a risolvere, invita ad aprire un ticket."
  }'
cURL — Windows CMD (Command Prompt)
curl -X POST "https://neuralai.pro/functions/triggerWebhookCall?token=YOUR_WEBHOOK_TOKEN" -H "Content-Type: application/json" -d "{\"to_number\": \"+393331234567\", \"recipient_name\": \"Mario Rossi\", \"topic\": \"Conferma appuntamento\", \"caller_on_behalf_of\": \"Studio Medico XYZ\", \"agent_name\": \"Lucy\", \"agent_instructions\": \"Non chiedere dati sensibili per telefono.\"}"
Python
import requests

WEBHOOK_URL = "https://neuralai.pro/functions/triggerWebhookCall?token=YOUR_WEBHOOK_TOKEN"

response = requests.post(WEBHOOK_URL, json={
    "to_number": "+393331234567",
    "recipient_name": "Mario Rossi",
    "topic": "Conferma appuntamento",
    "caller_on_behalf_of": "Studio Medico XYZ",
    "agent_name": "Lucy",
    "agent_instructions": "Non chiedere dati sensibili per telefono. Se non riesci a risolvere, invita ad aprire un ticket."
})

result = response.json()
print(f"Call ID: {result.get('call_id')}")
print(f"Success: {result.get('success')}")
JavaScript
const WEBHOOK_URL = "https://neuralai.pro/functions/triggerWebhookCall?token=YOUR_WEBHOOK_TOKEN";

const response = await fetch(WEBHOOK_URL, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    to_number: "+393331234567",
    recipient_name: "Mario Rossi",
    topic: "Conferma appuntamento",
    caller_on_behalf_of: "Studio Medico XYZ",
    agent_name: "Lucy",
    agent_instructions: "Non chiedere dati sensibili per telefono. Se non riesci a risolvere, invita ad aprire un ticket."
  })
});

const result = await response.json();
console.log("Call ID:", result.call_id);
console.log("Success:", result.success);
PowerShell
$webhookUrl = "https://neuralai.pro/functions/triggerWebhookCall?token=YOUR_WEBHOOK_TOKEN"

$body = @{
    to_number = "+393331234567"
    recipient_name = "Mario Rossi"
    topic = "Conferma appuntamento"
    caller_on_behalf_of = "Studio Medico XYZ"
    agent_name = "Lucy"
    agent_instructions = "Non chiedere dati sensibili per telefono."
} | ConvertTo-Json

$response = Invoke-RestMethod -Uri $webhookUrl -Method Post \
    -ContentType "application/json" -Body $body

Write-Host "Call ID: $($response.call_id)"
Write-Host "Success: $($response.success)"

Example response

{
  "success": true,
  "message": "Call triggered successfully",
  "call_id": "call_abc123...",
  "outbound_call_id": "uuid-...",
  "recipient_number": "+393331234567",
  "timestamp": "2026-07-08T10:30:00.000Z"
}