Webhooks
Configure webhooks to receive inbound messages and delivery updates
Webhooks allow your application to receive real-time notifications when events occur, such as incoming messages, delivery status changes, or partner invitation updates.
Zavu supports two types of webhooks:
- Sender Webhooks: Receive events for a specific sender (messages, conversations)
- Project Webhooks: Receive project-level events (partner invitations)
Project Webhooks
Project webhooks are ideal for receiving notifications about project-level events, such as when a partner invitation is completed. This is particularly useful if you're using the Partner Invitations API to onboard clients.
A project webhook receives events for its own project only. If you onboard each client in a sub-account, configure the webhook with that sub-account's API key: the parent project's webhook does not receive events from its sub-accounts.
Via Dashboard
- Go to Dashboard and select your project
- Navigate to Webhooks in the sidebar
- In the "Project Webhook" section, click Configure
- Enter your webhook URL and select the events you want to receive
- Save - you'll receive a signing secret
Via API
Configure a project webhook using the invitations API:
cURLcurl -X POST https://api.zavu.dev/v1/invitations/webhook \ -H "Authorization: Bearer zv_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/webhooks/zavu", "events": ["invitation.status_changed"] }'
Response:
JSON{ "webhook": { "url": "https://your-app.com/webhooks/zavu", "events": ["invitation.status_changed"], "secret": "whsec_abc123def456...", "active": true } }
Managing Project Webhooks
Get current configuration:
cURLcurl https://api.zavu.dev/v1/invitations/webhook \ -H "Authorization: Bearer zv_live_xxx"
Update webhook: only works on a webhook created with POST first. Updating one that does not exist returns 400.
cURLcurl -X PATCH https://api.zavu.dev/v1/invitations/webhook \ -H "Authorization: Bearer zv_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "url": "https://new-url.com/webhooks", "active": true }'
Remove webhook:
cURLcurl -X DELETE https://api.zavu.dev/v1/invitations/webhook \ -H "Authorization: Bearer zv_live_xxx"
Project Webhook Events
| Event | Description |
|---|---|
invitation.status_changed | A partner invitation status changed (in_progress, completed, failed, cancelled, or back to pending on resend). See the event reference |
Project Webhook Payload
JSON{ "id": "evt_1234567890_abc123", "type": "invitation.status_changed", "timestamp": 1705312200000, "projectId": "prj_xyz789", "data": { "invitationId": "inv_abc123", "clientName": "John Doe", "clientEmail": "john@example.com", "connectionType": "whatsapp_waba", "previousStatus": "in_progress", "currentStatus": "completed", "senderId": "snd_new123", "wabaAccountId": "waba_123456", "connectedAccount": { "channel": "whatsapp", "id": "109876543210", "name": "John Doe Store" } } }
Sender Webhooks
Each sender can have one webhook configured. Webhooks are managed as part of the sender resource.
Via Dashboard
- Go to Dashboard and select your project
- Navigate to your Sender's settings
- Click Add Webhook
- Enter your webhook URL and select the events you want to receive
- Save - you'll receive a signing secret
Via API
Configure a webhook when creating a new sender:
cURLcurl -X POST https://api.zavu.dev/v1/senders \ -H "Authorization: Bearer zv_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "name": "My Sender", "phoneNumber": "+15551234567", "webhookUrl": "https://your-app.com/webhooks/zavu", "webhookEvents": ["message.inbound", "message.delivered", "message.failed"] }'
Response:
JSON{ "id": "snd_abc123", "name": "My Sender", "phoneNumber": "+15551234567", "isDefault": false, "webhook": { "url": "https://your-app.com/webhooks/zavu", "events": ["message.inbound", "message.delivered", "message.failed"], "secret": "whsec_abc123def456...", "active": true }, "createdAt": "2024-01-15T10:30:00.000Z" }
Available Events
Inbound (Receiving Messages)
| Event | Description |
|---|---|
conversation.new | First message from a new contact (new conversation started) |
message.inbound | A customer sent you a message |
Outbound (Delivery Tracking)
| Event | Description |
|---|---|
message.queued | Your message was queued for delivery |
message.sent | Your message was sent to the carrier |
message.delivered | Your message was delivered to the recipient |
message.read | Your message was read by the recipient (WhatsApp only) |
message.failed | Message delivery failed |
Templates
| Event | Description |
|---|---|
template.status_changed | A WhatsApp template status changed (draft, pending, approved, rejected) |
Partner Invitations (Project Webhook)
| Event | Description |
|---|---|
invitation.status_changed | A partner invitation status changed (in_progress, completed, failed, cancelled, or back to pending on resend). See the event reference |
Webhook Payload
All webhook payloads follow the same structure:
JSON{ "id": "evt_1234567890_abc123", "type": "message.inbound", "timestamp": 1705312200000, "senderId": "snd_abc123", "projectId": "prj_xyz789", "data": { // Event-specific data } }
| Field | Description |
|---|---|
id | Unique event identifier |
type | Event type (e.g., message.inbound) |
timestamp | Unix timestamp (ms) when Zavu dispatched the webhook — not when the message was sent. For inbound messages, compare against data.providerTimestamp (the channel's original receive time) to detect delayed deliveries. |
senderId | The Sender that received this event |
projectId | Your project ID |
data | Event-specific payload |
Handling Webhooks
Your webhook endpoint must:
- Respond with 2xx status within 30 seconds
- Verify the signature to ensure the request is from Zavu
- Process asynchronously for long-running tasks
import express from "express";
const app = express();
app.post("/webhooks/zavu", express.json(), (req, res) => {
// 1. Verify signature (see Security guide)
const signature = req.headers["x-zavu-signature"];
if (!verifySignature(signature, req.body, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// 2. Acknowledge receipt immediately
res.status(200).send("OK");
// 3. Process the event asynchronously
const event = req.body;
processEvent(event).catch(console.error);
});
async function processEvent(event) {
switch (event.type) {
case "message.inbound":
await handleInboundMessage(event.data);
break;
case "message.delivered":
await handleDeliveryConfirmation(event.data);
break;
case "message.failed":
await handleDeliveryFailure(event.data);
break;
}
}
Retry Policy
If your endpoint returns an error or doesn't respond within 30 seconds, Zavu will retry the delivery:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 15 minutes |
| 4th retry | 1 hour |
| 5th retry | 4 hours |
After 5 failed attempts, the webhook delivery is marked as failed. You can view failed deliveries in the Dashboard.
Managing Webhooks
Update Webhook Configuration
Update your sender's webhook settings:
cURLcurl -X PATCH https://api.zavu.dev/v1/senders/snd_abc123 \ -H "Authorization: Bearer zv_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "webhookUrl": "https://new-url.com/webhooks", "webhookEvents": ["message.inbound"], "webhookActive": true }'
Disable Webhook
cURLcurl -X PATCH https://api.zavu.dev/v1/senders/snd_abc123 \ -H "Authorization: Bearer zv_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "webhookActive": false }'
Remove Webhook
Set webhookUrl to null to remove the webhook:
cURLcurl -X PATCH https://api.zavu.dev/v1/senders/snd_abc123 \ -H "Authorization: Bearer zv_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "webhookUrl": null }'
Regenerate Secret
If your webhook secret is compromised:
cURLcurl -X POST https://api.zavu.dev/v1/senders/snd_abc123/webhook/secret \ -H "Authorization: Bearer zv_live_xxx"
Response:
JSON{ "secret": "whsec_new_secret_here..." }
Next Steps
- Event Types - Detailed payload for each event
- Security - Verify webhook signatures