Webhook Security & Verification
Wagy uses webhooks to send incoming message data from WhatsApp in real-time to your server. To guarantee data security and integrity, we implement a two-way verification mechanism.
1. URL Ownership Verification (Handshake)
When you register or update a Webhook URL in the User Panel, Wagy will send a GET request to that URL to ensure that you are the legitimate owner of the endpoint.
- Method:
GET - Query Parameter:
challenge(random string)
Response Requirements
Your server must respond to the request with:
- Status code
200 OK. - The response body must contain only the value of the
challengeparameter (plain text).
Example (Node.js/Express):
app.get('/webhook', (req, res) => {
const challenge = req.query.challenge;
if (challenge) {
return res.status(200).send(challenge);
}
res.status(400).send('No challenge provided');
});
2. Signature Verification (HMAC-SHA256)
Once the URL is verified, Wagy will include the X-Wagy-Signature header in every POST request (message data). This header contains an HMAC-SHA256 signature created using your device's unique Webhook Secret.
The Webhook Secret will only be generated and appear in the User Panel after the URL has been successfully verified. Do not share this secret with anyone.
How to Verify Signature
To verify that the request truly originated from Wagy:
- Get the raw JSON body of the request.
- Calculate the HMAC-SHA256 of the body using your Webhook Secret.
- Compare the result with the value of the
X-Wagy-Signatureheader.
- Node.js
- PHP
const crypto = require('crypto');
app.post('/webhook', (req, res) => {
const signature = req.headers['x-wagy-signature'];
const secret = 'YOUR_WEBHOOK_SECRET_FROM_PANEL';
if (!signature) {
return res.status(401).send('Missing signature');
}
// Calculate HMAC-SHA256 from raw body
// Note: Make sure you get the raw body before it's parsed by body-parser
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(JSON.stringify(req.body)).digest('hex');
// Compare signature
if (signature !== digest) {
return res.status(401).send('Invalid signature');
}
// Signature valid, process data
console.log('Verified message:', req.body);
res.sendStatus(200);
});
<?php
$secret = 'YOUR_WEBHOOK_SECRET_FROM_PANEL';
$signature = $_SERVER['HTTP_X_WAGY_SIGNATURE'] ?? '';
// Get raw body from request
$payload = file_get_contents('php://input');
if (empty($signature)) {
http_response_code(401);
die('Missing signature');
}
// Calculate HMAC-SHA256
$computedSignature = hash_hmac('sha256', $payload, $secret);
// Compare signature (use hash_equals to prevent timing attacks)
if (!hash_equals($signature, $computedSignature)) {
http_response_code(401);
die('Invalid signature');
}
// Signature valid, process data
$data = json_decode($payload, true);
// ... your business logic ...
http_response_code(200);
3. Webhook Payload
Every POST request uses an Event-Based Payload structure for consistency across different types of events.
Main Structure
| Field | Type | Description |
|---|---|---|
event | string | Type of event (e.g., message.received). |
source | string | Event source (whatsapp, api, manual, system). |
data | object | Main data wrapper object. |
data.id | number | Unique webhook log ID (audit ID). |
data.device_id | string | Wagy device ID. |
data.content | object | Event-specific data (see examples below). |
Example Payloads by Event
Wagy uses an Event-Based Payload structure. The data.content object will have a different structure depending on the event type.
1. Incoming Message (Inbound)
- Event:
message.received,message.edited - Source:
whatsapp
{
"event": "message.received",
"source": "whatsapp",
"data": {
"id": 1001,
"device_id": "OFFICE-01",
"owner_jid": "62812345678@s.whatsapp.net",
"content": {
"pn_jid": "628999888777@s.whatsapp.net",
"lid_jid": "203998263034091@lid",
"content": "Hello, is stock ready?",
"message_id": "ABC123XYZ",
"timestamp": "2026-05-13T07:05:00Z",
"is_edit": false
},
"created_at": "2026-05-13T07:05:00Z"
}
}
2. Manual Outbound Message (Handset)
- Event:
message.manual_sent,message.manual_edited - Source:
manual
{
"event": "message.manual_sent",
"source": "manual",
"data": {
"id": 1002,
"device_id": "OFFICE-01",
"owner_jid": "62812345678@s.whatsapp.net",
"content": {
"pn_jid": "628999888777@s.whatsapp.net",
"lid_jid": "203998263034091@lid",
"content": "Ready!",
"message_id": "XYZ789ABC",
"timestamp": "2026-05-13T07:10:00Z",
"is_edit": false
},
"created_at": "2026-05-13T07:10:00Z"
}
}
3. API Delivery Status
- Event:
message.sent,message.delivered,message.read,message.failed,message.cancelled - Source:
api
{
"event": "message.delivered",
"source": "api",
"data": {
"id": 1003,
"device_id": "OFFICE-01",
"owner_jid": "62812345678@s.whatsapp.net",
"content": {
"message_id": "MSG_ID_WA_123",
"queue_id": 12345,
"pn_jid": "628999888777@s.whatsapp.net",
"lid_jid": "203998263034091@lid",
"content": "Thank you!",
"status": "DELIVERED"
},
"created_at": "2026-05-13T07:15:00Z"
}
}
4. Device Status
- Event:
device.status_update - Source:
system
{
"event": "device.status_update",
"source": "system",
"data": {
"id": 1004,
"device_id": "OFFICE-01",
"owner_jid": "62812345678@s.whatsapp.net",
"content": {
"device_id": "OFFICE-01",
"status": "CONNECTED",
"label": "Admin Sales"
},
"created_at": "2026-05-13T07:20:00Z"
}
}
Webhook Security
Wagy sends the X-Wagy-Signature header containing the HMAC-SHA256 of the request body. Use your Webhook Secret to verify the authenticity of the data.
Always verify the signature to ensure that the data truly originated from Wagy's server and is not a spoofing attack.