Skip to main content
The call.incoming webhook fires when a call starts. You can respond with a custom agent configuration to dynamically control how the AI handles the call.

Webhook Payload

{
  "type": "call.incoming",
  "data": {
    "from_number": "+15555550100",
    "to_number": "+15555550200",
    "call_id": 987654321
  }
}
FieldTypeDescription
from_numberstringCaller’s phone number
to_numberstringYour ThunderPhone number
call_idintegerUnique call identifier

Response

Respond with the agent configuration to use for this call. If you don’t respond (or return an empty response), ThunderPhone uses the default agent configured for the phone number.

Required Fields

{
  "prompt": "You are a helpful booking assistant for ABC Restaurant.",
  "voice": "en-US-Standard-Journey-D",
  "product": "spark"
}
FieldTypeRequiredDescription
promptstringYesSystem prompt for the AI agent
voicestringYesVoice ID for text-to-speech
productstringYesspark, bolt, or zap
background_trackstring | nullNonull or "CALL_CENTER"
toolsarrayNoFunction tools for the agent
voice_name is also accepted but normalized to voice. Unknown top-level keys are rejected.

Example Handler

from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib
import json

app = FastAPI()
WEBHOOK_SECRET = "your-webhook-secret"

def verify_signature(body: bytes, signature: str) -> bool:
    expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

@app.post("/thunderphone-webhook")
async def webhook(request: Request):
    body = await request.body()
    signature = request.headers.get("X-ThunderPhone-Signature", "")
    
    if not verify_signature(body, signature):
        raise HTTPException(status_code=401)
    
    event = json.loads(body)
    
    if event["type"] == "call.incoming":
        from_number = event["data"]["from_number"]
        
        # Customize prompt based on caller
        if from_number.startswith("+1415"):
            prompt = "Greet the caller as a San Francisco local..."
        else:
            prompt = "You are a friendly customer support agent..."
        
        return {
            "prompt": prompt,
            "voice": "en-US-Standard-Journey-D",
            "product": "spark"
        }
    
    return {"status": "ok"}

Response with Function Tools

Include tools to let the AI call your APIs during the conversation:
{
  "prompt": "You are a booking assistant. Use the available tools to help customers schedule appointments.",
  "voice": "en-US-Standard-Journey-D",
  "product": "spark",
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_appointments",
        "description": "Find available appointment slots",
        "parameters": {
          "type": "object",
          "properties": {
            "date": {
              "type": "string",
              "description": "Date in YYYY-MM-DD format"
            },
            "service": {
              "type": "string",
              "description": "Type of service"
            }
          },
          "required": ["date"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/search",
        "method": "POST",
        "headers": {
          "X-Api-Key": "your-key",
          "X-Request-Source": "ThunderPhone"
        }
      }
    }
  ]
}
The endpoint configuration tells ThunderPhone how to call your API when the AI invokes the tool. See Function Tools for details.

Product Options

ProductDescriptionBest For
sparkFastest response timesHigh-volume, simple interactions
boltBalanced speed and qualityGeneral use cases
zapHighest quality responsesComplex conversations