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

# Webhooks

> Platform guide for webhooks in HubTalk AI v4.

# Webhooks

Webhooks allow HubTalk AI v4 to send call data to your systems automatically after the call ends. Use them to power dashboards, CRM updates, or business automations.

## What webhooks send

A webhook delivers a JSON payload with information about the call, including:

* call status and duration
* agent details
* transcripts and summaries
* custom analysis variables

## How to enable webhooks

Set the webhook URL in the agent editor under the webhook section. The system will send data after each completed call or call attempt.

<img src="https://mintcdn.com/hubtalk/yYJS_d_pzapQzHCp/images/v4_webhooks_form_en.png?fit=max&auto=format&n=yYJS_d_pzapQzHCp&q=85&s=8c1c9caca205112f7dda12e4942b25fe" alt="The New webhook form: endpoint URL, events, the env variable name holding the signing secret, and the response timeout" width="720" height="522" data-path="images/v4_webhooks_form_en.png" />

### One endpoint belongs to one agent

Every webhook endpoint names an agent, and that agent must be a live agent of your organization. There is no organization-wide endpoint: a subscription without an agent name is refused with `webhooks.agent_required`, and an agent that is unknown or archived is refused with `webhooks.agent_not_found`.

<Warning>
  **There is no organization-wide subscription.** If your integration needs events for several agents, register the endpoint once per agent. A row with an empty agent name, if one sits in the database, is inert: it is not deleted, and it receives nothing.
</Warning>

To receive the same events for several agents, add the endpoint to each of them. The same URL may be reused: the payload carries `agent_id`, so one receiver can still handle them all.

## Webhook settings

An agent's webhooks live in the **🪝 Webhooks (post-call)** section of the settings panel in the builder. **Add webhook** opens a form with these fields:

* **Endpoint URL** — the HTTPS address that receives the notifications. `http://` is available only to the platform owner organization.
* **Events** — what to send:
  * **Call ended (`call_ended`)** — after the call finishes;
  * **Analytics ready (`call_analyzed`)** — when post-call analytics is ready.
* **Signing secret — env variable name** — only the reference is stored: the value is read from the environment and never hits the database or the logs.
* **Response timeout (sec)** — the maximum wait for your endpoint's response; `0` uses the pipeline default.
* **Enabled** — delivery can be paused without deleting the address.
* **Send test-session events** — off by default, so dashboard tests do not fire the webhook.

The form has no agent field of its own: the webhook belongs to the agent whose settings you created it in. A saved endpoint carries a **Test** button in the list, which sends a trial notification without waiting for a real call.

## Use cases

* send call results to a CRM
* update ticket status in support software
* trigger follow-up tasks when a customer agrees to pay
* store call summaries for compliance or quality review

## Verify the webhook

After configuration, use a test call or webhook inspector to confirm that the payload arrives at your endpoint and the data format matches your expectations.

## Example payload

Here is a sample `call_ended` webhook payload that HubTalk AI v4 may send to your endpoint:

```json theme={null}
{
  "event": "call_ended",
  "event_id": "evt-4f0c22b17a9d4e3c8b6a5f019e2d7c31",
  "timestamp": 1753344187.51,
  "call_id": "call-out-3f9a1b2c4d",
  "agent_id": "support-agent",
  "flow_version": "9f2c41a87d3b",
  "flow_version_id": 42,
  "metadata": { "crm_lead_id": "L-98421" },
  "call": {
    "channel": "phone",
    "mode": "live",
    "direction": "outbound",
    "from_number": "15550100",
    "to_number": "15551234567",
    "trunk_id": "trunk-ab12cd34ef",
    "started_at": 1753344000.123,
    "ended_at": 1753344187.4,
    "duration": 187.3,
    "status": "ended",
    "outcome": "completed",
    "call_status": "completed",
    "status_reason": "agent_hangup",
    "error_code": null,
    "message_left": false,
    "transcript": [
      { "role": "assistant", "text": "Hi Alex, this is the clinic calling…" },
      { "role": "user", "text": "Yes, that works for me." }
    ],
    "dynamic_variables": { "customer_name": "Alex", "confirmed": "yes" },
    "latency": { "total": { "avg": 812.4, "max": 1420.0 } },
    "recording_url": "",
    "recording": {
      "id": "rec-7c1d…",
      "status": "pending",
      "recording_url": "",
      "channels": "",
      "format": "mp3",
      "duration": null,
      "size": null,
      "expires_at": null
    }
  }
}
```

## Second event: analytics ready

HubTalk AI v4 can send a second webhook once post-call analytics finish processing. The `call_analyzed` event contains the complete call object plus the analysis results, so a subscriber only needs this one event to get the full picture — subscribing to `call_ended` as well is optional.

```json theme={null}
{
  "event": "call_analyzed",
  "event_id": "evt-9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6",
  "timestamp": 1752750007.42,
  "call_id": "c1e5a2b4-7f3d-4e8a-9b06-2d51c0ffee11",
  "agent_id": "demo_bilingual",
  "flow_version": "3a1f9c2b7d4e",
  "flow_version_id": 7,
  "call": {
    "channel": "phone",
    "mode": "live",
    "direction": "inbound",
    "started_at": 1752749875.4,
    "ended_at": 1752749998.9,
    "duration": 123.5,
    "status": "ended",
    "call_status": "completed",
    "status_reason": "agent_hangup",
    "error_code": null,
    "transcript": [{ "role": "assistant", "text": "Hi there, how can I help?" }],
    "dynamic_variables": { "customer_name": "Alex", "slot": "10:00" }
  },
  "analysis_status": "done",
  "call_analysis": {
    "summary": "The customer booked an appointment for tomorrow at 10:00; the agent confirmed the booking.",
    "sentiment": {
      "label": "positive",
      "score": 0.6,
      "caller_label": "neutral",
      "trajectory": ["neutral", "positive"]
    },
    "custom_fields": { "appointment_date": "2026-07-18", "service": "consultation" },
    "call_successful": true
  },
  "analytics_tier": "mass",
  "analytics_model_used": "google/gemma-4-26B-A4B-it",
  "analytics_language": "en"
}
```

Both events carry an **`error_code`** field — the numeric failure code assigned by the telecom operator (for example `486` with `status_reason` = `busy`). It is `null` when the operator reported no code. Operators interpret these codes differently — do not build strict logic on them without checking with your own operator. Note as well that `busy` and `user_declined` are distinct reasons: a busy line and a subscriber hanging up are told apart.

If analysis was skipped or failed, `call_analysis` is empty and a `reason` field explains why, for example `voicemail` or `no_speech`. When the call has a recording, `call_analyzed` also includes a top-level `recording_url` and a `recording` object once the recording is ready — `call_ended` carries the same recording block, but it is often still processing at that point.

## Unanswered calls carry variables too

A call that never connected — no answer, busy, a failure, an answering machine — still produces a webhook, and that webhook carries `dynamic_variables`: the values you supplied for the contact when the call was queued. Nothing was collected during the conversation, because there was no conversation, but the input of the call is there.

<Warning>
  **Do not use an empty `dynamic_variables` as a no-answer signal.** The variables arrive both on a connected call and on an unanswered one, so an empty dictionary says nothing about the outcome. Branch on `call_status` and `status_reason` instead — those are the fields that state it.
</Warning>

Three surfaces show the same thing: the call variable inside post-call functions, the dictionary in the call-ended webhook, and `GET /v1/calls/{call_id}` in the [Web API](/v4/web-api) — for one call the data agrees.

## Keep it simple

Start with a single webhook URL and a small set of fields. Once the integration works, expand the payload to include more analytics and variables.
