> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nedzo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Post-conversation webhook

> Send conversation data to your server after a voice, chat, or web agent finishes. Includes the transcript, summary, and extracted fields.

Every agent can fire an outbound webhook the moment a conversation ends. Use it to push transcripts, summaries, dispositions, and extracted fields into your own CRM, data warehouse, or notification system.

Previously called the "Post-Call Webhook" (voice only). It now fires for all three agent types:

| Agent type | When it fires                                                                    |
| ---------- | -------------------------------------------------------------------------------- |
| Voice      | After a phone call ends (inbound or outbound)                                    |
| Chat       | After an SMS, Instagram, Messenger, email, or web chat conversation is finalized |
| Web Agent  | After a web agent conversation (voice or chat) ends                              |

## Setup

### In the dashboard

1. Open your agent and go to the **Settings** tab.
2. Under **Post-Conversation Webhook**, paste your endpoint URL and save.
3. Click **Test webhook** to fire a sample payload so you can verify your server accepts it.

### Via API

Set `postConversationWebhookUrl` when creating or updating an agent:

```bash theme={null}
curl -X PATCH "https://api.nedzo.ai/v1/agents/{agentId}" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"postConversationWebhookUrl": "https://example.com/webhooks/conversation-completed"}'
```

Set the field to `null` to disable the webhook.

<Note>
  The legacy field name `postCallWebhookUrl` is still accepted on create/update for backwards compatibility, but API responses always return `postConversationWebhookUrl`.
</Note>

## Delivery semantics

* **Fire-and-forget.** Nedzo sends the request but does not retry on failure.
* **Timeout.** The request is aborted if your server takes longer than 10 seconds to respond.
* **Success.** Any `2xx` status is logged as delivered. Non-2xx responses and timeouts are logged but do not retry.
* **Order.** One webhook per conversation, sent after the conversation has been persisted, summary/extraction has run, and the workflow trigger has been queued.
* **URL validation.** Only `https://` (or `http://` for public hosts in development) is allowed. Internal IPs and metadata endpoints are rejected to prevent SSRF.
* **HIPAA.** If the agent has HIPAA compliance enabled, `transcript` is omitted from the payload and `summary` is sent as `null`.

## Payload

Nedzo sends a `POST` request with `Content-Type: application/json` and a JSON body.

### Common fields

Every payload includes these fields, regardless of channel:

| Field                     | Type              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `event`                   | string            | `"conversation.completed"` for chat and web. Voice sends `"call.completed"` for backwards compatibility.                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `channel`                 | string            | One of `voice`, `web`, `sms`, `email`, `whatsapp`, `instagram`, `facebook`, `web_chat`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `conversationId`          | string (UUID)     | The Nedzo conversation record ID.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `workspaceId`             | string (UUID)     | The workspace the agent belongs to.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `startedAt`               | string (ISO 8601) | When the conversation started.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `endedAt`                 | string (ISO 8601) | When the conversation ended.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `agent`                   | object            | `{ agentId, name, phone? }`. Use `agent.agentId` to identify the agent. `phone` only included for voice.                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `contact`                 | object \| null    | Contact record, or `null` when no contact was matched/created. Always a fully populated object when present — never an empty `{}`. Voice/web include `{ contactId, firstName, lastName, phone, email, contactBusinessName, tags, customFields }`; chat includes `{ contactId }`. Values in `customFields` are typed JSON primitives (see [Field-value typing](#field-value-typing)).                                                                                                                                                                               |
| `analysis.summary`        | string \| null    | AI-generated summary. `null` if HIPAA is enabled or summary generation failed.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `analysis.evaluation`     | boolean \| null   | Success evaluation result if configured.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `analysis.disposition`    | string \| null    | AI-classified disposition label when call disposition classification is enabled on the agent.                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `analysis.dataExtraction` | object            | Map of configured extraction field names to typed JSON values (`string` / `number` / `boolean` / `null`). See [Field-value typing](#field-value-typing).                                                                                                                                                                                                                                                                                                                                                                                                           |
| `transcript`              | string            | Full transcript, one conversation turn per line, separated by newlines (`\n`). For **text channels** (SMS, WhatsApp, Instagram, Facebook, web chat, email) each line is prefixed with a `[YYYY-MM-DD HH:mm:ss]` timestamp in your workspace timezone, e.g. `[2026-06-15 14:23:07] User: ...`. **Voice and web** transcripts are not timestamped and use the plain `Speaker: text` form. In both cases each line is `Speaker: text` (`User:` or `Assistant:`). The field name and string type are unchanged. **Omitted entirely when HIPAA compliance is enabled.** |

### Voice & Web Agent fields

| Field             | Type                      | Description                                                                                                                                                                              |
| ----------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `direction`       | string                    | `"inbound"` or `"outbound"`.                                                                                                                                                             |
| `durationSeconds` | number                    | Call/session duration in seconds.                                                                                                                                                        |
| `endedReason`     | string \| null            | Raw reason the call ended (e.g. `customer-ended-call`, `no-answer`, `voicemail`, `assistant-forwarded-call`). Use this for fine-grained branching.                                       |
| `agent.phone`     | string \| null            | The phone number the agent used (voice only).                                                                                                                                            |
| `appointmentDate` | string (ISO 8601) \| null | Confirmed appointment date/time if a calendar booking tool fired during the conversation. Resolved to UTC using the booking tool's configured timezone. `null` when no booking happened. |
| `actions`         | array                     | Canonical action log of every tool call the agent executed during the conversation. See [Actions log](#actions-log) below. Empty array when HIPAA is enabled.                            |

### Chat-only fields

| Field           | Type                  | Description                                                                                                |
| --------------- | --------------------- | ---------------------------------------------------------------------------------------------------------- |
| `messageCount`  | number                | Number of messages exchanged.                                                                              |
| `lastMessageAt` | string (ISO 8601)     | Timestamp of the final message.                                                                            |
| `contactId`     | string (UUID) \| null | Contact record, if one was matched/created. Chat payloads also include this convenience field at the root. |

### Web Agent fields

Web Agent payloads use the voice-style structure — `endedReason`, `durationSeconds` on the root — plus `channel: "web"`.

## Example payloads

### Voice call

```json theme={null}
{
  "event": "call.completed",
  "channel": "voice",
  "conversationId": "0a9b8c7d-...",
  "workspaceId": "w1w2w3-...",
  "direction": "outbound",
  "endedReason": "customer-ended-call",
  "durationSeconds": 245,
  "startedAt": "2026-04-23T14:30:00Z",
  "endedAt": "2026-04-23T14:34:05Z",
  "contact": {
    "contactId": "a1b2c3d4-...",
    "firstName": "John",
    "lastName": "Doe",
    "phone": "+14155551234",
    "email": "john@example.com",
    "contactBusinessName": "Acme Co",
    "tags": ["lead", "interested"],
    "customFields": { "budget": 5000, "is_returning": true, "tier": "gold" }
  },
  "agent": {
    "agentId": "x1y2z3-...",
    "name": "Sales Agent",
    "phone": "+14155550001"
  },
  "analysis": {
    "evaluation": true,
    "summary": "Customer confirmed interest and booked a demo for Friday at 2pm.",
    "disposition": "Appointment Booked",
    "dataExtraction": { "budget": 5000, "timeline": "Q3 2026", "demo_requested": true }
  },
  "appointmentDate": "2026-04-26T18:00:00.000Z",
  "actions": [
    {
      "type": "appointment_booked",
      "at": "2026-04-23T14:33:47Z",
      "metadata": { "scheduledFor": "2026-04-26T18:00:00.000Z", "calendarName": "Sales calendar" }
    }
  ],
  "transcript": "User: Hi, I'm calling about the demo.\nAssistant: Hello John, happy to help. Are you free Friday?\nUser: Friday at 2pm works.\nAssistant: Great, you're booked for Friday at 2pm."
}
```

Each turn sits on its own line (`Speaker: text`), separated by `\n`. Split on newlines to reconstruct the turn-by-turn conversation.

### Actions log

`actions` is a chronological array of every tool call the agent ran during the conversation. Each entry has:

| Field      | Type              | Description                                                                                                                       |
| ---------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `type`     | string            | One of `appointment_booked`, `call_transfer`, `call_ended_by_assistant`, `sms_sent`, `email_sent`, `custom_action`, `mcp_action`. |
| `at`       | string (ISO 8601) | When the action fired.                                                                                                            |
| `metadata` | object            | Type-specific payload — booking details, transfer destination, message body, etc. Internal IDs are stripped.                      |

Failed tool calls are not emitted. The full unredacted action log (including tool args) is kept on the conversation record itself; `actions` here is sanitized for outbound delivery.

When **HIPAA compliance** is enabled on the agent, `actions` is sent as an empty array since tool arguments can carry protected health information.

### Chat conversation (SMS / Instagram / email / web chat)

```json theme={null}
{
  "event": "conversation.completed",
  "channel": "sms",
  "conversationId": "0a9b8c7d-...",
  "workspaceId": "w1w2w3-...",
  "contactId": "a1b2c3d4-...",
  "messageCount": 8,
  "startedAt": "2026-04-23T14:30:00Z",
  "endedAt": "2026-04-23T14:45:00Z",
  "lastMessageAt": "2026-04-23T14:45:00Z",
  "contact": {
    "contactId": "a1b2c3d4-..."
  },
  "agent": {
    "agentId": "x1y2z3-...",
    "name": "Support Agent"
  },
  "analysis": {
    "summary": "Visitor asked about pricing tiers and agreed to a follow-up email.",
    "disposition": "Interested - Demo Scheduled",
    "dataExtraction": { "interest": "pricing", "seats_needed": 12 }
  },
  "transcript": "[2026-04-23 14:30:12] User: How much does this cost?\n[2026-04-23 14:31:05] Assistant: Our plans start at $49/mo.\n[2026-04-23 14:43:20] User: Can you email me the details?\n[2026-04-23 14:43:58] Assistant: Sure, sending those over now."
}
```

Text-channel transcripts prefix each line with `[YYYY-MM-DD HH:mm:ss] ` in your workspace timezone (no offset suffix). If a workspace hasn't set a timezone, times render in UTC. Strip the leading `[...] ` to recover the plain `Speaker: text` line.

### Web Agent conversation

```json theme={null}
{
  "event": "conversation.completed",
  "channel": "web",
  "conversationId": "0a9b8c7d-...",
  "workspaceId": "w1w2w3-...",
  "endedReason": "customer-ended-call",
  "durationSeconds": 132,
  "startedAt": "2026-04-23T14:30:00Z",
  "endedAt": "2026-04-23T14:32:12Z",
  "contact": {
    "contactId": "a1b2c3d4-...",
    "firstName": "Jane",
    "lastName": "Smith",
    "phone": null,
    "email": "jane@example.com",
    "contactBusinessName": null,
    "tags": [],
    "customFields": {}
  },
  "agent": {
    "agentId": "x1y2z3-...",
    "name": "Website Assistant"
  },
  "analysis": {
    "evaluation": true,
    "summary": "Visitor asked about onboarding and asked for a demo link.",
    "disposition": "Interested - Demo Scheduled",
    "dataExtraction": { "intent": "demo_request", "team_size": 25 }
  },
  "appointmentDate": null,
  "actions": [
    {
      "type": "email_sent",
      "at": "2026-04-23T14:32:05Z",
      "metadata": { "to": "jane@example.com", "subject": "Your demo link", "body": "Hi Jane, here's the link..." }
    }
  ],
  "transcript": "User: Hi, I was looking at your pricing page.\nAssistant: Happy to help. What size is your team?\nUser: About 25 people.\nAssistant: I'll send over a demo link tailored to that."
}
```

## Field-value typing

`analysis.dataExtraction` and `contact.customFields` ship values as the JSON primitive that matches each field's declared type. The mapping:

| Declared type             | JSON output                 | Example raw value               | Example output            |
| ------------------------- | --------------------------- | ------------------------------- | ------------------------- |
| `number`                  | `number` (finite) or `null` | `"42"` / `"3.14"` / `"-7"`      | `42` / `3.14` / `-7`      |
| `number` (unparseable)    | `null`                      | `"abc"` / `""` / `"NaN"`        | `null`                    |
| `boolean`                 | `true` / `false` / `null`   | `"true"` / `"TRUE"` / `"False"` | `true` / `true` / `false` |
| `boolean` (non-canonical) | `null`                      | `"yes"` / `"1"` / `""`          | `null`                    |
| `text` / `string`         | `string`                    | `"gold"`                        | `"gold"`                  |
| `date`                    | `string` (ISO 8601)         | `"2026-05-20"`                  | `"2026-05-20"`            |
| unset / unknown           | `string` (raw)              | `"anything"`                    | `"anything"`              |

Notes:

* A field that is not yet populated for the conversation/contact is omitted from the map rather than emitted as `null`.
* `null` in `customFields` / `dataExtraction` always means *the stored value could not be coerced to the declared type* — log it on your side if you care about type drift.
* Booleans accept canonical `'true'` / `'false'` only (case-insensitive, trimmed). Legacy non-canonical values like `'yes'` / `'1'` are intentionally surfaced as `null` instead of silently flipped to `false`.
* Dates are emitted as the raw stored string (typically ISO 8601). JSON has no native date type.

## Security

* **Always use HTTPS.** Nedzo blocks requests to non-public IPs and metadata endpoints but you should also refuse plaintext HTTP on your end.
* **Verify the source.** Nedzo does not currently sign outbound webhooks. If you need to verify the sender, host your endpoint behind an auth gateway, allowlist Nedzo IP ranges, or include a shared secret in the URL path.
* **Be idempotent.** Use `conversationId` as your idempotency key so duplicate deliveries (e.g. from network retries on your side) don't create duplicate records.

## Related

* [Conversation ended workflow trigger](/workflows/triggers/conversation-completed) — to branch Nedzo workflows after any voice, chat, or web conversation, use this trigger instead of the webhook.
* [Workflow webhook trigger](/workflows/triggers/webhook) — for sending requests *into* Nedzo from your own systems.
