Webhooks
Webhook subscriptions use a RestHooks-style API. Zapier creates and deletes subscriptions automatically when a user enables or disables a trigger.
Subscribe
POST /api/v1/webhooks/subscribe
Authorization: Bearer lzy_at_your_access_token
Content-Type: application/json
{
"target_url": "https://hooks.zapier.com/hooks/catch/123/abc/",
"event_types": ["lead.created"],
"filters": {
"chatbot_id": "optional-chatbot-id"
}
}
The response returns the subscription and a signing secret. The secret is shown only once.
List and Delete
| Endpoint | Method |
|---|---|
/api/v1/webhooks | GET |
/api/v1/webhooks/:id | GET |
/api/v1/webhooks/:id | DELETE |
All webhook endpoints require the webhooks:manage scope.
Event Types
| Event | Description |
|---|---|
lead.created | A new lead was captured. |
lead.updated | A lead's fields changed. |
lead.qualified | A lead reached the qualification threshold. |
lead.converted | A lead was marked as converted to a customer. |
lead.score_changed | A lead's qualification score changed. |
conversation.started | A new conversation was opened. |
conversation.ended | A conversation was closed. |
conversation.message_received | A new message arrived in a conversation. |
conversation.sentiment_negative | A conversation reached negative sentiment. |
conversation.sentiment_positive | A conversation reached positive sentiment. |
message.feedback_negative | A user gave negative feedback on a message. |
message.feedback_positive | A user gave positive feedback on a message. |
ticket.created | A support ticket was created. |
ticket.updated | A support ticket was updated. |
ticket.resolved | A support ticket was marked as resolved. |
meeting.scheduled | A meeting was booked. |
meeting.cancelled | A meeting was cancelled. |
meeting.completed | A meeting was marked as completed. |
meeting.reminder | A reminder for an upcoming meeting fired. |
agent.tool_executed | An AI agent tool was executed. |
agent.tool_failed | An AI agent tool execution failed. |
usage.threshold_reached | The workspace hit a quota threshold. |
Delivery Security
Leezy sends each webhook with these headers:
| Header | Purpose |
|---|---|
X-Leezy-Signature | HMAC SHA-256 signature over <timestamp>.<raw_body>, prefixed with sha256=. |
X-Leezy-Timestamp | Unix timestamp (seconds) included in the signed input. |
X-Leezy-Event | Event type. |
X-Leezy-Delivery | Delivery log ID. |
To verify a delivery, recompute the HMAC over ${timestamp}.${raw_body} using the subscription secret and compare with X-Leezy-Signature. The timestamp is part of the signed input — receivers MUST also reject deliveries whose X-Leezy-Timestamp is more than five minutes away from the receiver's current time. Without the timestamp window, a captured payload could be replayed indefinitely because the same body always produces the same signature.
Verification example (Node.js):
import crypto from "node:crypto";
function verifyLeezySignature(rawBody, headers, secret) {
const timestamp = headers["x-leezy-timestamp"];
const signature = headers["x-leezy-signature"];
const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!timestamp || ageSeconds > 300) return false;
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}