Skip to content

Webhook Response

Build call flow instructions using typed Pydantic verbs.

How It Works

When jambonz receives a call, it sends an HTTP POST to your webhook URL. You respond with a list of verbs that tell jambonz what to do.

from jambonz import JambonzResponse, Say, Hangup

# Your response is a list of verbs
verbs = [Say(text="Hello!"), Hangup()]

# Wrap in JambonzResponse for serialization
response = JambonzResponse(verbs)
response.model_dump()
# → [{"verb": "say", "text": "Hello!"}, {"verb": "hangup"}]

Available Verbs

Say

Text-to-speech:

Say(text="Hello world")

# With specific TTS vendor
Say(
    text="Hola mundo",
    synthesizer=Synthesizer(
        vendor="elevenlabs",
        language="es",
        voice="pNInz6obpgDQGcFmaJgB",
        label="elevenlabs_bookline",
    ),
)

Play

Play an audio file:

Play(url="https://example.com/greeting.mp3")

Gather

Collect speech or DTMF input:

Gather(
    input=[Input.SPEECH, Input.DIGITS],
    action_hook="/handle-input",
    timeout=10,
    num_digits=4,
    recognizer=Recognizer(vendor="google", language="en-US"),
    say=Say(text="Please say your account number"),
)

Pause

Wait:

Pause(length=2.0)

Hangup

End the call:

Hangup()

Redirect

Transfer to a different webhook:

Redirect(action_hook="/new-handler")

LLM

Connect to a conversational AI:

LLM(
    vendor="elevenlabs",
    auth=LLMAuth(agent_id="your-agent-id"),
    llm_options={},
    action_hook="/llm-complete",
    tool_hook="/tool-call",
)

Dial

Dial out to a number:

Dial(
    action_hook="/dial-status",
    caller_id="+34931228497",
    timeout=30,
    target=[{"type": "phone", "number": "+34600000000"}],
)

Building Responses with Logic

from jambonz import JambonzResponse, Say, Gather, Hangup, Input

def handle_call(is_business_hours: bool) -> JambonzResponse:
    verbs = [Say(text="Welcome to our service.")]

    if is_business_hours:
        verbs.append(
            Gather(
                input=[Input.SPEECH],
                action_hook="/menu",
                say=Say(text="How can I help you today?"),
            )
        )
    else:
        verbs.extend([
            Say(text="We are currently closed. Please call back during business hours."),
            Hangup(),
        ])

    return JambonzResponse(verbs)

Serialization

The JambonzResponse automatically handles:

  • Excludes null fields — jambonz rejects payloads with null values
  • camelCase aliasesaction_hook becomes actionHook
  • Enum serializationInput.SPEECH becomes "speech"
response = JambonzResponse([
    Gather(input=[Input.SPEECH], action_hook="/resp"),
])

response.model_dump()
# → [{"verb": "gather", "input": ["speech"], "actionHook": "/resp", "timeout": 10}]
# Note: no nulls, camelCase keys, enum as string

response.model_dump_json()
# → '[{"verb":"gather","input":["speech"],"actionHook":"/resp","timeout":10}]'

The Verb Type

All verbs are part of a discriminated union:

from jambonz import Verb

# Verb = Say | Play | Gather | Pause | Hangup | Redirect | LLM | Dial
# Discriminated on the "verb" field

This means Pydantic can automatically parse a verb dict into the correct type:

from pydantic import TypeAdapter

adapter = TypeAdapter(Verb)
verb = adapter.validate_python({"verb": "say", "text": "Hello"})
# → Say(verb='say', text='Hello')