Webhooks
We POST a signed JSON event to your HTTPS endpoint when something happens in any product.
Delivery
- Body:
{ "id", "type", "timestamp", "api_version", "org_id", "data" }.idequals thewebhook-idheader and stays the same on retries and replays: use it to ignore duplicates. - Answer with any 2xx within 10 seconds. Do slow work after answering. Redirects are not followed.
- Retries after a failure: 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 10 h (with jitter), 8 attempts in total. Then the delivery is marked failed and you can replay it from the dashboard.
- HTTP 410 Gone turns the endpoint off at once; 20 failed deliveries in a row turn it off too. Turning it back on resets the counter.
- Only public HTTPS addresses are called (no private networks, no cloud metadata addresses), and the address is checked again on every delivery.
- Order is not guaranteed. Use
timestamp, or fetch the current object from the API.
Verify the signature
We follow Standard Webhooks. Compute HMAC-SHA256 over webhook-id.webhook-timestamp.body with the base64 part of your whsec_ secret, and compare it with each v1, value in webhook-signature. Reject timestamps older than 5 minutes. While you rotate a secret, both the new and the old secret sign each delivery.
import { createHmac, timingSafeEqual } from "node:crypto";
// Use the RAW request body (before JSON parsing).
export function verify(secret, headers, rawBody) {
const id = headers["webhook-id"], ts = headers["webhook-timestamp"], sigs = headers["webhook-signature"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) throw new Error("too old");
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = createHmac("sha256", key).update(`${id}.${ts}.${rawBody}`).digest();
const ok = sigs.split(" ").some((s) => {
const got = Buffer.from(s.split(",")[1] ?? "", "base64");
return got.length === expected.length && timingSafeEqual(got, expected);
});
if (!ok) throw new Error("bad signature");
return JSON.parse(rawBody);
}Webhook tester
Paste a delivery from your logs to check its signature. Runs in your browser only.
Event catalogue
Subscribe to exact events, a whole product (lead.*) or everything (*). Also as JSON: /api/v1/events.
Contacts
- contact.created
- A contact was added through the API.
- contact.updated
- A contact was changed through the API.
- contact.deleted
- A contact was deleted through the API.
AI Front Desk
- message.received
- A customer message arrived on any channel.
- message.created
- A message was added to a conversation.
- message.queued
- An outgoing message was queued for delivery.
- message.sent
- An outgoing message was sent by the channel provider.
- message.failed
- An outgoing message could not be delivered.
- message.bounced
- An email bounced.
- channel.opted_out
- A person opted out of a channel (STOP, unsubscribe).
- booking.created
- A booking was created through the API.
- booking.updated
- A booking was changed through the API.
- booking.requested
- A booking was requested by the assistant or a customer.
- booking.reminder_queued
- A booking reminder was queued.
- booking.reminder_sent
- A booking reminder was sent.
- booking.reminder_skipped
- A booking reminder was skipped.
- calendar.external_cancelled
- A booking was cancelled in the connected calendar.
- calendar.external_moved
- A booking was moved in the connected calendar.
- calendar.external_conflict
- A connected calendar has a conflicting event.
- knowledge_article.created
- A knowledge article was added through the API.
- knowledge_article.updated
- A knowledge article was changed through the API.
- knowledge_article.deleted
- A knowledge article was deleted through the API.
AI Revenue Engine
- lead.created
- A new lead was created.
- lead.updated
- A lead was changed through the API.
- lead.stage_changed
- A lead moved to another pipeline stage.
- lead.assigned
- A lead got a new owner.
- followup.enrolled
- A lead was enrolled in a follow-up sequence.
- followup.drafted
- A follow-up message was drafted.
- followup.stopped
- A follow-up sequence stopped for a lead.
- campaign.created
- An old-lead campaign was created.
AI Voice Workforce
- voice.call_started
- A phone call started.
- voice.call_ended
- A phone call ended (summary, outcome).
- voice.message_taken
- The voice agent took a message.
- voice.transfer_requested
- A caller asked to speak to a person.
- voice.dial_requested
- An outbound campaign target is ready to dial.
- voice.campaign_created
- An outbound call campaign was created.
- voice.dnc_added
- A number was added to the do-not-call list.
AI Customer Experience
- ticket.created
- A support ticket was created.
- ticket.updated
- A ticket was changed through the API.
- ticket.status_changed
- A ticket changed status.
- ticket.assigned
- A ticket got a new assignee or priority.
- ticket.merged
- A ticket was merged into another.
- ticket.sla_breached
- A ticket missed its SLA target.
- ticket.limit_reached
- The monthly ticket limit was reached.
- csat.sent
- A CSAT survey was sent.
- csat.received
- A CSAT score was received.
- review.received
- A review was added.
- review.negative
- A negative review needs attention.
AI Sales Workforce
- prospect.created
- A prospect was added through the API.
- prospect.updated
- A prospect was changed through the API.
- sales.imported
- Prospects were imported.
- sales.enrolled
- A prospect was enrolled in an outbound sequence.
- sales.drafted
- An outbound step was drafted.
- sales.message_sent
- An outbound message was sent.
- sales.reply_logged
- A prospect replied.
- sales.reply_interested
- A prospect replied with interest.
- sales.reply_handed_over
- A reply was handed over to a person.
- sales.meeting_booked
- A meeting was booked with a prospect.
- sales.meeting_scheduled
- A meeting was scheduled.
- sales.meeting_held
- A meeting took place.
- sales.meeting_no_show
- A prospect did not attend a meeting.
- sales.meeting_cancelled
- A meeting was cancelled.
- sales.enrolment_stopped
- An outbound sequence stopped for a prospect.
- sales.opted_out
- A prospect opted out.
- sales.suppression_added
- An address or domain was added to do-not-contact.
AI Business Intelligence
- bi.insight_created
- An alert rule produced an insight.
- bi.digest_ready
- The weekly digest is ready.
- bi.dataset_imported
- A dataset was imported.
- bi.dashboard_created
- A dashboard was created.
- bi.question_asked
- Someone asked a question about the data.
AI Back-Office Workforce
- back_office.document_uploaded
- A document was uploaded.
- back_office.extraction_done
- A document was read (fields extracted).
- back_office.document_approved
- A document was approved.
- back_office.document_rejected
- A document was rejected.
- back_office.approval_requested
- An approval step is waiting.
- back_office.record_approved
- A record (bill, invoice, expense) was approved.
- back_office.record_rejected
- A record was rejected.
- back_office.record_status_changed
- A record changed status (scheduled, paid).
AI Content Studio
- content.post_created
- A post was created through the API.
- content.post_updated
- A post was changed through the API.
- content.plan_generated
- A content plan was generated.
- content.post_submitted
- A post was submitted for approval.
- content.post_approved
- A post was approved.
- content.post_changes_requested
- Changes were requested on a post.
- content.post_scheduled
- A post was scheduled.
- content.post_published
- A post was marked as published.
- content.ideas_refreshed
- Content ideas were refreshed.
- content.review_consent_withdrawn
- A customer withdrew consent to feature a review.