Skip to main content

Webhooks

Zapy API sends real-time notifications to your server when events occur on your WhatsApp instances. Configure a single webhook URL to receive events from all your instances.

User-Level Webhooks​

Webhooks are configured at the user/account level, not per-instance. This means:

  • One webhook URL receives events from all your instances
  • Simplified management - configure once, receive all events
  • Built-in retry queue with automatic retries and pause/resume
  • Webhook verification (Facebook-style challenge-response)
  • HMAC-SHA256 signature for security

Each webhook payload includes instanceId to identify which instance generated the event.

How Webhooks Work​

  1. Configure your webhook URL in the Dashboard Settings
  2. Verify your webhook using the Facebook-style challenge verification
  3. Zapy sends HTTP POST requests to your URL when events occur
  4. Your server processes the event and responds with a 2xx status code

Webhook Configuration​

Setting Up Your Webhook​

  1. Go to Dashboard Settings
  2. Enter your HTTPS webhook URL
  3. (Optional) Set a webhook secret for signature verification
  4. Click Verify to complete the challenge-response verification
  5. Enable your webhook

Webhook Verification​

Before receiving events, you must verify ownership of your webhook URL. Zapy uses a Facebook-style challenge-response verification:

  1. When you click "Verify", Zapy sends a GET request to your URL with:

    • hub.mode=subscribe
    • hub.verify_token=<random_token>
    • hub.challenge=<random_challenge>
  2. Your endpoint must respond with exactly the hub.challenge value

Example verification handler (Node.js/Express):

app.get('/webhook', (req, res) => {
const mode = req.query['hub.mode'];
const challenge = req.query['hub.challenge'];

if (mode === 'subscribe') {
// Return the challenge to verify ownership
res.status(200).send(challenge);
} else {
res.status(400).send('Invalid request');
}
});

Webhook Payload Structure​

All webhook payloads follow this structure:

{
"event": "message",
"instanceId": "your-instance-id",
"data": {
// Event-specific data
}
}
FieldTypeDescription
eventstringThe event type identifier
instanceIdstringThe WhatsApp instance ID that triggered the event
dataobjectEvent-specific payload data

Available Events​

EventDescription
messageNew message received (text, media, polls, calls, etc.)
message-statusMessage delivery status update
qr-codeQR code generated for authentication
instance-statusInstance connection status changed
contact-createdNew contact discovered
contact-updatedContact information updated
contact-deduplicatedDuplicate contacts merged (LID-to-phone mapping)

Webhook Security​

Signature Verification​

If you configure a webhook secret, Zapy will sign all webhook payloads using HMAC-SHA256. The signature is included in the X-Webhook-Signature header.

Header format:

X-Webhook-Signature: sha256=<signature>

Verification example (Node.js):

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');

return signature === `sha256=${expectedSignature}`;
}

// In your webhook handler
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const isValid = verifyWebhookSignature(req.body, signature, YOUR_SECRET);

if (!isValid) {
return res.status(401).send('Invalid signature');
}

// Process the webhook...
res.status(200).send('OK');
});

Using the SDK helper:

import { verifyWebhookSignature } from '@zapyapi/sdk';

app.post('/webhook', express.json(), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const isValid = verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET);

if (!isValid) {
return res.status(401).send('Invalid signature');
}

// Process the webhook...
res.status(200).send('OK');
});

Request Headers​

All webhook requests include these headers:

HeaderValue
Content-Typeapplication/json
User-AgentZapyAPI-Webhook/1.0
X-Webhook-Signaturesha256=<signature> (if secret configured)

Type-Safe Webhook Handling​

Use the SDK's enums and type guards for type-safe webhook handling:

import {
ZapyWebhookPayload,
ZapyEventTypes,
ZapyMessageStatusEnum,
InstanceStatus,
isTextMessage,
isImageMessage
} from '@zapyapi/sdk';

function handleWebhook(payload: ZapyWebhookPayload) {
switch (payload.event) {
case ZapyEventTypes.MESSAGE:
// Handle incoming message
const message = payload.data;
console.log('Message from:', message.sender.name);

if (isTextMessage(message)) {
console.log('Text:', message.text);
} else if (isImageMessage(message)) {
console.log('Image caption:', message.caption);
}
break;

case ZapyEventTypes.MESSAGE_STATUS:
// Handle message status update
if (payload.data.status === ZapyMessageStatusEnum.READ) {
console.log('Message was read!');
}
break;

case ZapyEventTypes.INSTANCE_STATUS:
// Handle instance status change
console.log(`Instance ${payload.data.instanceId} status: ${payload.data.status}`);
if (payload.data.status === InstanceStatus.CONNECTED) {
console.log('Instance is now connected!');
}
break;

case ZapyEventTypes.QR_CODE:
// Handle QR code
console.log('QR Code:', payload.data.qr);
break;

case ZapyEventTypes.REACTION:
// Handle message reaction
console.log('Reaction:', payload.data.reaction);
break;

case ZapyEventTypes.PRESENCE:
// Handle presence update
console.log('Presence:', payload.data.presence);
break;

case ZapyEventTypes.CONTACT_DEDUPLICATED:
// Handle contact deduplication (important for syncing contacts locally)
console.log('Kept contact:', payload.data.keptContactId);
console.log('Deleted contact:', payload.data.deletedContactId);
// Update your database to merge these contacts
break;
}
}

Retry Queue & Failure Handling​

Zapy includes a built-in retry queue for failed webhook deliveries:

  1. Initial delivery attempt - Immediate
  2. Retry attempts - Up to 5 retries with exponential backoff
  3. Pause on repeated failures - If too many failures occur, your webhook is paused
  4. Resume - You can resume paused webhooks from the dashboard

Queue Status​

Monitor your webhook queue status in the dashboard:

  • Pending - Webhooks waiting to be delivered
  • Failed - Webhooks that failed after all retries
  • Paused - Webhooks paused due to repeated failures
  • Delivered - Successfully delivered webhooks

Best Practices​

  1. Respond quickly - Return a 2xx status code within 10 seconds
  2. Process asynchronously - Queue events for background processing
  3. Verify signatures - Always validate the webhook signature in production
  4. Handle duplicates - Use the messageId to deduplicate events
  5. Use HTTPS - Webhook URLs must use HTTPS
  6. Keep secrets secure - Store your webhook secret securely
  7. Route by instanceId - Use the instanceId field to handle events from different instances