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_changedanddeal.wonordeal.lost. - Leads and deals brought in by a CRM import send no events.
The body
lead is the whole lead as the API 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 returns it |
number is the lead’s number and line is yours.
{
"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"
}
}Verify the signature
Deliveries are signed to the Standard Webhooks 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:
Verify against the raw bytes you received. Parsing the JSON and writing it out again changes the body and breaks the signature.
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");
}Delivery and retries
- We post what is due every minute.
- Any
2xxanswer 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.
© 2026 TalaLevel · talalevel.com · openapi.json · llms.txt · Privacy · Terms