conversation.ended

The conversation ended webhook is triggered when conversation ends.

Webhook payload

Here is an example of the POST request JSON payload:

{
"event_type": "conversation.ended",
"data": {
"conversation": {
"id": "conv_894dcb66-c3dd-4160-8606-30387e8ab9b5",
"agent": {
"id": "agent_123",
"name": "Customer Support",
"is_deleted": false
},
"workspace": "phonic",
"project": {
"id": "proj_8e5bdac5-868d-46fa-b397-439e777f7bfd",
"name": "main"
},
"external_id": null,
"model": "merritt",
"welcome_message": "Hey, how can I help?",
"template_variables": {},
"input_format": "pcm_44100",
"output_format": "pcm_44100",
"live_transcript": "Hey, how can I help?",
"post_call_transcript": null,
"duration_ms": 46537,
"background_noise_level": 0.1,
"background_noise": null,
"boosted_keywords": null,
"pronunciation_dictionary": [{"word": "Phuket", "pronunciation": "Poo-ket"}],
"min_words_to_interrupt": 1,
"default_language": "en",
"additional_languages": [],
"multilingual_mode": "request",
"push_to_talk": false,
"no_input_poke_sec": null,
"no_input_poke_text": null,
"no_input_end_conversation_sec": null,
"audio_url": "...",
"started_at": "2025-07-14T11:35:40.617Z",
"ended_at": "2025-07-14T11:36:27.154Z",
"ended_by": "user",
"task_results": {},
"items": [
{
"item_idx": 0,
"role": "assistant",
"live_transcript": "Hey, how can I help?",
"voice_id": "grant",
"system_prompt": "Help the user to book a dentist appointment.",
"audio_speed": 1,
"duration_ms": 26.71,
"started_at": "2025-07-14T11:35:41.717Z",
"tool_calls": []
}
]
},
"call_info": {
"from_phone_number": "+17124583766",
"to_phone_number": "+19189397081"
}
},
"created_at": "2025-07-14T11:36:33.768Z"
}

Payload fields

event_type
stringRequired

Always "conversation.ended"

created_at
stringRequired

ISO 8601 timestamp of when the event was created

data.conversation
ConversationRequired

The full conversation object. See the Get Conversation endpoint for all fields.

data.call_info
CallInfo | null

Phone call metadata (present for telephony conversations, null for web). Contains from_phone_number, to_phone_number, and optionally twilio_call_sid.

Example usage

Here’s an example of how to handle the webhook:

import { Hono } from "hono";
import { Webhook } from "svix";
import type { Phonic } from "phonic";
const app = new Hono();
app.post("/webhooks/phonic", async (c) => {
if (!process.env.PHONIC_WEBHOOK_SECRET) {
return c.text("Bad Request", 400);
}
const wh = new Webhook(process.env.PHONIC_WEBHOOK_SECRET);
const rawBody = await c.req.text();
try {
const payload = wh.verify(rawBody, {
"svix-id": c.req.header("svix-id") ?? "",
"svix-timestamp": c.req.header("svix-timestamp") ?? "",
"svix-signature": c.req.header("svix-signature") ?? "",
}) as Phonic.ConversationEndedWebhookPayload;
const conversation = payload.data.conversation;
console.log(`Conversation ${conversation.id} ended`);
return c.text("OK", 200);
} catch (error) {
console.error("Failed to verify webhook:", error);
return c.text("Bad Request", 400);
}
});
export default app;