conversation.ended

The conversation ended webhook is triggered when conversation ends.

Webhook payload

Here is an example of the POST request JSON payload:

1{
2 "event_type": "conversation.ended",
3 "data": {
4 "conversation": {
5 "id": "conv_894dcb66-c3dd-4160-8606-30387e8ab9b5",
6 "agent": {
7 "id": "agent_123",
8 "name": "Customer Support",
9 "is_deleted": false
10 },
11 "workspace": "phonic",
12 "project": {
13 "id": "proj_8e5bdac5-868d-46fa-b397-439e777f7bfd",
14 "name": "main"
15 },
16 "external_id": null,
17 "model": "merritt",
18 "welcome_message": "Hey, how can I help?",
19 "template_variables": {},
20 "input_format": "pcm_44100",
21 "output_format": "pcm_44100",
22 "live_transcript": "Hey, how can I help?",
23 "post_call_transcript": null,
24 "duration_ms": 46537,
25 "background_noise_level": 0.1,
26 "background_noise": null,
27 "boosted_keywords": null,
28 "pronunciation_dictionary": [{"word": "Phuket", "pronunciation": "Poo-ket"}],
29 "min_words_to_interrupt": 1,
30 "default_language": "en",
31 "additional_languages": [],
32 "multilingual_mode": "request",
33 "push_to_talk": false,
34 "no_input_poke_sec": null,
35 "no_input_poke_text": null,
36 "no_input_end_conversation_sec": null,
37 "audio_url": "...",
38 "started_at": "2025-07-14T11:35:40.617Z",
39 "ended_at": "2025-07-14T11:36:27.154Z",
40 "ended_by": "user",
41 "task_results": {},
42 "items": [
43 {
44 "item_idx": 0,
45 "role": "assistant",
46 "live_transcript": "Hey, how can I help?",
47 "voice_id": "grant",
48 "system_prompt": "Help the user to book a dentist appointment.",
49 "audio_speed": 1,
50 "duration_ms": 26.71,
51 "started_at": "2025-07-14T11:35:41.717Z",
52 "tool_calls": []
53 }
54 ]
55 },
56 "call_info": {
57 "from_phone_number": "+17124583766",
58 "to_phone_number": "+19189397081"
59 }
60 },
61 "created_at": "2025-07-14T11:36:33.768Z"
62}

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:

1import { Hono } from "hono";
2import { Webhook } from "svix";
3import type { Phonic } from "phonic";
4
5const app = new Hono();
6
7app.post("/webhooks/phonic", async (c) => {
8 if (!process.env.PHONIC_WEBHOOK_SECRET) {
9 return c.text("Bad Request", 400);
10 }
11
12 const wh = new Webhook(process.env.PHONIC_WEBHOOK_SECRET);
13 const rawBody = await c.req.text();
14
15 try {
16 const payload = wh.verify(rawBody, {
17 "svix-id": c.req.header("svix-id") ?? "",
18 "svix-timestamp": c.req.header("svix-timestamp") ?? "",
19 "svix-signature": c.req.header("svix-signature") ?? "",
20 }) as Phonic.ConversationEndedWebhookPayload;
21
22 const conversation = payload.data.conversation;
23 console.log(`Conversation ${conversation.id} ended`);
24
25 return c.text("OK", 200);
26 } catch (error) {
27 console.error("Failed to verify webhook:", error);
28
29 return c.text("Bad Request", 400);
30 }
31});
32
33export default app;