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

# Webhooks: Get Notified When Conversations Complete

> Configure a ListenLand webhook to receive real-time HTTP notifications whenever a respondent completes an interview conversation on your account.

ListenLand can POST a signed JSON payload to your endpoint whenever a respondent finishes a conversation. Use webhooks to trigger data pipelines, send follow-up emails, import responses into your own tools, or fan out to any downstream system the moment new feedback arrives.

## Setting up a webhook

<Steps>
  <Step title="Open your interview">
    Navigate to the interview you want to connect in your ListenLand dashboard.
  </Step>

  <Step title="Go to the Integrations section">
    Open the **Integrations** (or **Webhooks**) tab inside the interview editor.
  </Step>

  <Step title="Enter your endpoint URL">
    Paste the HTTPS URL that ListenLand should POST events to, e.g. `https://your-app.com/webhooks/listenland`.
  </Step>

  <Step title="Save and copy your secret">
    Click **Save**. ListenLand will display your webhook secret — a `whsec_…` string. **Copy it now.** It is shown only once and cannot be retrieved later. Store it in your secrets manager or environment variables immediately.
  </Step>

  <Step title="Send a test event">
    Use the **Send Test Event** button to fire a sample payload at your endpoint. The test event has the same shape as a real event, so you can build and validate your receiver without completing a real conversation.
  </Step>
</Steps>

## Requirements

| Requirement   | Detail                                                                                                        |
| ------------- | ------------------------------------------------------------------------------------------------------------- |
| **Protocol**  | HTTPS only (HTTP is rejected in production)                                                                   |
| **Host**      | Must be a public DNS hostname — IP addresses and internal hostnames (e.g. `.local`, `.internal`) are rejected |
| **Response**  | Your endpoint must return a `2xx` HTTP status within **10 seconds**                                           |
| **Redirects** | Not followed — use the exact destination URL                                                                  |

## The `conversation.completed` event

This is the only event type sent for real conversations. A `test` event (with identical structure and placeholder data) is also available via the **Send Test Event** button in the dashboard.

The full JSON payload looks like this:

```json theme={null}
{
  "event": "conversation.completed",
  "timestamp": "2024-11-15T14:32:00.000Z",
  "data": {
    "conversationId": "abc123def456ghi",
    "serialId": "xK9mP2qR7s",
    "interviewId": "def456uvw789xyz",
    "interviewTitle": "Onboarding experience feedback",
    "respondent": {
      "firstName": "Jane",
      "lastName": "Smith",
      "email": "jane@example.com"
    },
    "mode": "voice",
    "status": "complete",
    "title": "Frustrated by setup, loves the editor",
    "summary": "Jane found the onboarding clear but felt the email confirmation step was confusing. She praised the editor's simplicity and would recommend the product.",
    "url": "https://listen.land/c/xK9mP2qR7s",
    "startedAt": "2024-11-15T14:28:57.000Z",
    "durationMs": 183000,
    "messages": [
      {
        "role": "assistant",
        "content": "Hi! Thanks for joining. Can you tell me about your onboarding experience?",
        "createdAt": "2024-11-15T14:28:57.000Z"
      },
      {
        "role": "user",
        "content": "It was mostly smooth, but the email confirmation step confused me.",
        "createdAt": "2024-11-15T14:29:14.000Z"
      }
    ]
  }
}
```

### Payload fields

| Field                 | Type                | Description                                                                               |
| --------------------- | ------------------- | ----------------------------------------------------------------------------------------- |
| `event`               | `string`            | Always `"conversation.completed"` for real events; `"test"` for test events               |
| `timestamp`           | `string`            | ISO 8601 timestamp of when the event was dispatched                                       |
| `data.conversationId` | `string`            | Unique ID of the conversation                                                             |
| `data.serialId`       | `string`            | Short public ID — appears in the conversation URL                                         |
| `data.interviewId`    | `string`            | ID of the interview this conversation belongs to                                          |
| `data.interviewTitle` | `string \| null`    | Title of the interview                                                                    |
| `data.respondent`     | `object \| null`    | `firstName`, `lastName`, and `email` if collected; `null` if the interview didn't ask     |
| `data.mode`           | `"voice" \| "text"` | Conversation mode chosen by the respondent                                                |
| `data.status`         | `string`            | `"complete"` for completed conversations                                                  |
| `data.title`          | `string \| null`    | AI-generated title summarising the conversation                                           |
| `data.summary`        | `string \| null`    | AI-generated paragraph summary                                                            |
| `data.url`            | `string`            | Public URL to view this conversation in ListenLand                                        |
| `data.startedAt`      | `string`            | ISO 8601 timestamp of when the conversation began                                         |
| `data.durationMs`     | `number`            | Time between the first and last message in milliseconds                                   |
| `data.messages`       | `array`             | Transcript — each item has `role` (`"assistant"` or `"user"`), `content`, and `createdAt` |

## Request headers

Every request ListenLand sends includes the following headers:

| Header                   | Example value             |
| ------------------------ | ------------------------- |
| `Content-Type`           | `application/json`        |
| `X-ListenLand-Event`     | `conversation.completed`  |
| `X-ListenLand-Signature` | `t=1731681120,v1=a3f9c2…` |

The `X-ListenLand-Signature` header contains a Unix timestamp and an HMAC-SHA256 signature. See [Verify Webhook Signatures](/integrations/webhook-verification) for full details on how to validate it.

## Retry behavior

Events are delivered once — ListenLand makes a single POST attempt per event. Failures are logged but not retried. Ensure your endpoint is reliable, responds with `2xx` promptly, and handles any queuing or retry logic on your side if needed.

<Tip>
  Use the **Send Test Event** button in the dashboard to verify your endpoint before going live — it sends a realistic payload with the same shape as a real event, without needing a real conversation to complete.
</Tip>

***

<Card title="Verify webhook signatures" icon="shield-check" href="/integrations/webhook-verification">
  Learn how to validate the `X-ListenLand-Signature` header and protect against replay attacks.
</Card>
