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

# Webhooks

> Receive each new task at your own HTTPS endpoint the moment Answering Agent creates it.

A webhook sends each new task to your system as an HTTPS `POST`. Use it to open a ticket in Zendesk, create a card in ClickUp, or page a manager, without polling the [Customer API](/api/overview).

Answering Agent sends one event today, `task.created`. It fires when the AI creates a task from a conversation or a website form submission.

## Set up the webhook

Team owners and admins manage the webhook. Each team has one webhook address.

1. Go to **Settings → Webhooks** in the dashboard.
2. Paste your endpoint address under **Endpoint address** and save.
3. Copy the **Secret**. Your endpoint uses it to check that requests come from Answering Agent.
4. Click **Send test**. The page shows whether the test was delivered, and the error when it was not.

The address must:

* Start with `https://`.
* Use a public domain name. IP addresses, `localhost`, and `.local` or `.internal` names are rejected.
* Leave out a username and password.

To turn the webhook off, clear the address and save. Tasks created while the webhook is off are not sent later.

## Check the secret

Every request carries the secret in the `X-Answering-Agent-Secret` header. Compare it with the secret from **Settings → Webhooks** and reject any request where it does not match.

The header holds the secret itself, not a signature. Answering Agent does not sign or hash the body. Anyone who has the secret can send requests your endpoint accepts, so store it like a password.

```javascript JavaScript (Express) theme={null}
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const secret = Buffer.from(process.env.ANSWERING_AGENT_WEBHOOK_SECRET);

app.post('/answering-agent', express.json(), (req, res) => {
  const received = Buffer.from(req.get('X-Answering-Agent-Secret') ?? '');
  if (received.length !== secret.length || !crypto.timingSafeEqual(received, secret)) {
    return res.sendStatus(401);
  }

  const event = req.body;
  if (event.event === 'task.created' && event.data) {
    // Create the ticket from event.data. Skip event.id values you already processed.
  }
  res.sendStatus(200);
});
```

### Change the secret

Saving a new address keeps the same secret. To get a new secret, clear the address and save, then enter the address again and save. The page then shows a new secret. Update your endpoint before you send the next test.

<Warning>
  Clearing the address turns the webhook off. Tasks created while it is off are never sent, and retries still pending for earlier tasks stop. Answering Agent does not queue them for later.

  After you save the address again, fetch the tasks you missed with [`GET /tasks?range=today`](/api/tasks#search-tasks). If the webhook was off across midnight in your team's timezone, also fetch `range=yesterday`. Skip any task your system already has.
</Warning>

## The `task.created` event

```http theme={null}
POST /answering-agent HTTP/1.1
Content-Type: application/json
X-Answering-Agent-Secret: 3q2-7wZk1yHf0aLr9TbXc4VdN8sMpQe2
```

```json theme={null}
{
  "id": "conversation:184532:task.created",
  "event": "task.created",
  "occurred_at": "2026-10-05T14:21:07.000Z",
  "team_id": 410,
  "location": {
    "phone_number_id": 2181,
    "name": "Westside Wash",
    "phone_number": "+15125550142"
  },
  "task": {
    "source_type": "conversation",
    "source_id": 184532,
    "channel": "phone_call",
    "title": "Cancel monthly membership",
    "description": "Dana Ruiz wants to cancel her Unlimited membership before the charge on the 12th. She is moving out of state and declined the free-month offer. Confirm the cancellation in the POS and reply by text.",
    "category": "Retention",
    "url": "https://answeringagent.com/dashboard/conversations/184532"
  },
  "caller": {
    "name": "Dana Ruiz",
    "phone": "+15125550118"
  },
  "data": {
    "id": "conversation-184532",
    "url": "https://answeringagent.com/dashboard/conversations/184532",
    "title": "Cancel monthly membership",
    "status": "open",
    "assignee": { "id": 9034, "name": "Priya Shah" },
    "category": "cancellation",
    "priority": "high",
    "created_at": "2026-10-05T14:21:07.000Z",
    "customer": { "name": "Dana Ruiz", "phone": "+15125550118", "email": "dana.ruiz@example.com" },
    "conversation": {
      "id": 184532,
      "url": "https://answeringagent.com/dashboard/conversations/184532",
      "channel": "phone",
      "phone_line": { "id": 2181, "name": "Westside Wash", "phone_number": "+15125550142" }
    },
    "form": null,
    "description": "Dana Ruiz wants to cancel her Unlimited membership before the charge on the 12th. She is moving out of state and declined the free-month offer. Confirm the cancellation in the POS and reply by text."
  }
}
```

Read the task from `data`. `location`, `task`, and `caller` are [legacy fields](#legacy-fields) kept for integrations built before `data` existed.

<ResponseField name="id" type="string">
  A stable ID for this event, in the form `{source_type}:{source_id}:task.created`. Every retry of the same event has the same `id`. Use it to skip duplicates.
</ResponseField>

<ResponseField name="event" type="string">
  `task.created`, or `webhook.test` for the test button.
</ResponseField>

<ResponseField name="occurred_at" type="string">
  When the conversation or form submission came in, as an ISO 8601 UTC timestamp.
</ResponseField>

<ResponseField name="team_id" type="integer">
  Your team ID.
</ResponseField>

<ResponseField name="data" type="object | null">
  The task, the same object [`GET /tasks/{task_id}`](/api/tasks#get-a-task) returns, with its `id`, `status`, `assignee`, `category`, `priority`, `customer`, source `conversation` or `form`, and `description`. It uses the API's [names and IDs](/api/overview#names-and-ids). Answering Agent reads the task after any automatic assignment and again for each retry, so `data` shows the task as it is when that request is sent. `null` when the dashboard does not show the task.
</ResponseField>

### Legacy fields

`location`, `task`, and `caller` came before `data`. They keep their original names and values so existing integrations keep working. Read `data` in new code. The legacy values differ from `data` in these ways:

* `task.channel` uses the stored channel names `phone_call`, `sms`, `chat_widget`, and `email`. `data.conversation.channel` uses `phone`, `text`, `web_chat`, and `email`.
* `task.category` is a display label from a separate task category list, such as `Retention`. `data.category` is the conversation's category key, such as `cancellation`.
* `task.title` and `task.description` come from the first follow-up the AI wrote. `data.title` and `data.description` are what the dashboard shows for the task, and they can differ.
* `task.source_type` and `task.source_id` identify the source. `data.id` is already the API task ID, such as `conversation-184532`.

<ResponseField name="location" type="object">
  The phone line the task came from: `phone_number_id`, `name`, and `phone_number`. Each can be `null`, for example for a form submission that is not tied to a line.
</ResponseField>

<ResponseField name="task" type="object">
  <Expandable title="task fields">
    <ResponseField name="source_type" type="string">
      `conversation` or `form_submission`.
    </ResponseField>

    <ResponseField name="source_id" type="integer">
      The conversation ID or form submission ID. The API task ID is `conversation-{source_id}` or `form-submission-{source_id}`, the same as `data.id`.
    </ResponseField>

    <ResponseField name="channel" type="string | null">
      For a conversation: `phone_call`, `sms`, `chat_widget`, or `email`. `null` for a form submission.
    </ResponseField>

    <ResponseField name="title" type="string">
      A short title. `New task` when the AI wrote none.
    </ResponseField>

    <ResponseField name="description" type="string">
      What the team needs to do.
    </ResponseField>

    <ResponseField name="category" type="string">
      A task category as a display label, such as `Retention` or `Damage Claim`. `General` when there is none. This list is separate from the category keys in `data.category`.
    </ResponseField>

    <ResponseField name="url" type="string">
      The conversation or form submission in the dashboard.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="caller" type="object">
  The customer's `name` and `phone`. Either can be `null`.
</ResponseField>

## The `webhook.test` event

**Send test** posts a short event with the same headers. It has no `location`, `task`, `caller`, or `data`.

```json theme={null}
{
  "id": "webhook:410:test:1791297234567",
  "event": "webhook.test",
  "occurred_at": "2026-10-06T14:33:54.567Z",
  "team_id": 410
}
```

Return a `2xx` status so the test counts as delivered.

## Delivery and retries

* **Response.** Return any `2xx` status within 8 seconds. Do the slow work after you respond.
* **Retries.** A `5xx`, `408`, or `429` response, a timeout, or a connection error counts as a failed attempt. Answering Agent retries a failed delivery up to 4 more times, waiting longer between attempts. It ignores a `Retry-After` header. After the last failed retry, the event is dropped. Use [`GET /tasks`](/api/tasks#search-tasks) to find tasks your endpoint missed.
* **No retry.** Other responses, including `3xx` and `4xx`, end delivery for that event. Answering Agent does not follow redirects, so point the webhook at the final address.
* **Duplicates.** Your endpoint can get the same event more than once, for example when it saved the task but answered after the timeout. Skip any `id` you already processed.
* **Status.** **Settings → Webhooks** shows when the last delivery succeeded or failed, and the last error.

Tasks that are not new do not trigger the event. Examples are a repeat call that Answering Agent links to an existing task, or a conversation merged into another one.

## Other events

`task.created` is the only event today. To read anything else, such as transcripts, task status changes, or report numbers, call the [Customer API](/api/overview).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.