Webhook API
API reference for passthrough mode webhooks
Overview
In passthrough mode, chans sends transcribed speech to your webhook and expects a text response.
Request Format
POST /your-webhook-url
chans sends a POST request with the following JSON body:
{ "type": "transcription_complete", "session_id": "room-abc123", "timestamp": "2024-01-15T10:30:00.000000", "data": { "transcript": "What's my order status?", "user_id": "end-user-123", "context": null }}
Fields
| Field | Type | Description |
|---|---|---|
type | string | Event type (see Event Types below) |
session_id | string | Unique session/room identifier |
timestamp | string | ISO 8601 timestamp |
data.transcript | string | Transcribed user speech |
data.user_id | string | null | End user identifier (from SDK connection) |
data.context | string | null | RAG context if enabled on the agent |
Response Format
Your webhook must respond with JSON:
{ "response": "Your order #1234 is out for delivery!"}
Response Fields
| Field | Type | Description |
|---|---|---|
response | string | Text to be spoken back to the user |
Timeout
Your webhook has 30 seconds to respond by default. If your webhook times out, the user hears nothing for that turn and the agent continues listening.
Authentication
If you configure an API key in your agent settings, chans includes it in the request:
Authorization: Bearer your-api-key
Error Handling
If your webhook returns a non-2xx status code:
- The error is logged
- The user hears no response for that turn
- The agent continues listening for the next utterance
Example Implementation
Node.js / Express
app.post('/webhook', async (req, res) => { const { type, session_id, data } = req.body const { transcript, user_id } = data
// Call your LLM const response = await yourLLM.chat(transcript, { userId: user_id, sessionId: session_id })
res.json({ response: response.text })})
Python / FastAPI
@app.post("/webhook")async def webhook(payload: dict): transcript = payload["data"]["transcript"] user_id = payload["data"].get("user_id") session_id = payload["session_id"]
# Call your LLM response = await your_llm.chat( transcript, user_id=user_id, session_id=session_id )
return {"response": response}
Event Types
The main passthrough call uses transcription_complete (synchronous -- expects a response). chans also supports async events sent to a separate events URL:
| Event Type | Sync/Async | Description |
|---|---|---|
transcription_complete | Sync | User speech transcribed. Expects {"response": "..."} |
context_retrieved | Async | RAG context was retrieved for the query |
llm_response | Async | Agent response generated |
audio_ready | Async | TTS audio generated |
session_start | Async | Voice session started |
session_end | Async | Voice session ended |
Async events are fire-and-forget notifications sent to your configured events URL. They include retry with exponential backoff (up to 3 retries). Configure the events URL separately from your main webhook URL in agent settings.