---
title: "Webhooks"
description: "TalaLevel webhooks: eleven events for leads, texts, calls, meetings, payments and deals, the JSON body, the Standard Webhooks signature and the retry schedule."
url: https://talalevel.com/docs/webhooks/
date: 2026-11-12
updated: 2026-11-12
---

# Webhooks

Add up to ten https addresses in Settings → Connections → API and webhooks and pick the events each wants. We post a signed JSON body within about a minute of the event and retry eight times over about a day. Signatures follow the Standard Webhooks spec.

## Add an endpoint

An owner opens **Settings → Connections → API and webhooks**, adds an https address and ticks the events it wants.

- Up to ten endpoints in a workspace.
- Each gets a secret that starts with `whsec_`, shown once. You can make a new one at any time; the old one stops signing at once.
- An endpoint can be paused. What was queued waits for it.
- You can send a test event, `ping`, from the screen to check the address and the secret.
- The screen keeps a log of every delivery for a month, with the answer your server gave.

## Events

| Event | When | `data` holds |
| --- | --- | --- |
| `lead.created` | A lead is made, by any means: a person, an import, your website form, the API, Tala | `lead` |
| `lead.status_changed` | A lead's status changes | `lead`, `from`, `to` |
| `text.received` | A lead texts one of your numbers | `lead`, `text` |
| `call.ended` | A call ends, a rep's or the AI caller's | `lead`, `call` |
| `meeting.booked` | A call is booked on the calendar | `lead`, `meeting` |
| `payment.received` | A payment becomes paid | `lead`, `payment` |
| `deal.opened` | A deal is opened | `lead`, `deal`, `stage` |
| `deal.stage_changed` | A deal moves to another stage | `lead`, `deal`, `from`, `to` |
| `deal.won` | A deal moves into a won stage | `lead`, `deal`, `stage` |
| `deal.lost` | A deal moves into a lost stage | `lead`, `deal`, `stage` |
| `deal.cancelled` | A won sale is marked cancelled | `lead`, `deal`, `on` |
| `ping` | You send a test from the screen | `message` |

Worth knowing:

- A status set again to the same value is not a change, and sends nothing.
- A text is an event when it comes in. Texts you send are not events.
- A move into a won or lost stage sends `deal.stage_changed` and `deal.won` or `deal.lost`.
- Leads and deals brought in by a CRM import send no events.

## The body

```json
{
  "id": "evt_…",
  "type": "lead.status_changed",
  "created_at": "2026-11-12T09:44:02.000Z",
  "workspace_id": 7,
  "data": {
    "lead": { "id": 123, "name": "Reed Auto Repair", "status": "Interested" },
    "from": "New",
    "to": "Interested"
  }
}
```

`lead` is the whole lead as the [API](https://talalevel.com/docs/api/#leads) returns it, or `null` if it has since been removed. The example above is shortened.

| Object | Fields |
| --- | --- |
| `text` | `id`, `body`, `at`, `number`, `line` |
| `call` | `id`, `direction`, `at`, `duration`, `disposition`, `number`, `line` |
| `meeting` | `id`, `starts_at`, `ends_at`, `time_zone`, `attendee_email`, `meet_url`, `state` |
| `payment` | `id`, `amount`, `currency`, `kind`, `email` |
| `deal` | The whole deal as the [API](https://talalevel.com/docs/api/#pipelines-and-deals) returns it |

`number` is the lead's number and `line` is yours.

## Verify the signature

Deliveries are signed to the [Standard Webhooks](https://www.standardwebhooks.com/) spec. Three headers come with each one:

| Header | Value |
| --- | --- |
| `webhook-id` | `msg_…`. The same on every retry, so you can drop a duplicate by it. |
| `webhook-timestamp` | Seconds since 1970. |
| `webhook-signature` | `v1,` and the base64 HMAC-SHA256 of `{id}.{timestamp}.{body}` with your secret. |

Any Standard Webhooks library checks it. In Node:

```js
import { Webhook } from "standardwebhooks";

const wh = new Webhook(process.env.TALALEVEL_WEBHOOK_SECRET); // whsec_…

export async function POST(request) {
  const body = await request.text(); // the raw body, before any JSON parsing
  let event;
  try {
    event = wh.verify(body, Object.fromEntries(request.headers));
  } catch {
    return new Response("bad signature", { status: 400 });
  }
  if (event.type === "text.received") {
    // event.data.lead, event.data.text
  }
  return new Response("ok");
}
```

Verify against the raw bytes you received. Parsing the JSON and writing it out again changes the body and breaks the signature.

## Delivery and retries

- We post what is due every minute.
- Any `2xx` answer counts as delivered. Answer fast and do slow work afterwards; a delivery times out after 10 seconds.
- Anything else, a timeout or a redirect is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, then 10 hours twice. That is eight tries over about a day.
- Every retry carries the same body and the same `webhook-id`.
- After the last try the delivery is marked failed. You can send it again from the log.
- Redirects are never followed.

## Addresses we refuse

The address must be `https`, with no name and password in it. `localhost`, private network ranges and internal names are refused, both when you save the address and each time we connect. To test on your own machine, use a tunnel that gives you a public https address.

## Questions

### Which events does TalaLevel send webhooks for?

lead.created, lead.status_changed, text.received, call.ended, meeting.booked, payment.received, deal.opened, deal.stage_changed, deal.won, deal.lost and deal.cancelled, plus a ping test event.

### How do I verify a TalaLevel webhook?

Deliveries are signed to the Standard Webhooks spec with your endpoint's whsec_ secret. Use any standardwebhooks library with the webhook-id, webhook-timestamp and webhook-signature headers and the raw body.

### What happens if my endpoint is down?

We retry after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, then 10 hours twice: eight tries over about a day. After that the delivery is marked failed and can be sent again from the log.
