API Reference
Payloads & Signature
Payload field reference and HMAC verification for inbound webhook deliveries.
Message event payload
Used by message.received, message.sent, message.delivered, message.read, and message.failed.
message.received — example
{
"event": "message.received",
"timestamp": "2026-07-10T12:00:00+03:00",
"data": {
"message_id": 300541,
"whatsapp_message_id": "3EB0XXXX",
"conversation_id": 20717,
"account_id": 74,
"channel_id": 74,
"device_id": "8_abc123xyz",
"channel_type": "device",
"direction": "inbound",
"status": "unknown",
"message_type": "text",
"text": "Hello from WhatsApp",
"media_url": null,
"sender_phone": "966500000000",
"contact": {
"id": 4821,
"name": "Ahmed Ali",
"phone": "966500000000"
},
"sent_at": "2026-07-10T12:00:00+03:00",
"timestamp": "2026-07-10T12:00:00+03:00",
"updated_at": "2026-07-10T12:00:00+03:00"
}
}
| Field | Type | Description |
|---|---|---|
message_id |
integer | Internal Washeej message ID. |
whatsapp_message_id |
string | WhatsApp message ID when available. |
conversation_id |
integer | Conversation this message belongs to. |
account_id |
integer | WhatsApp account (channel) ID. |
channel_id |
integer | Same as account_id — channel identifier. |
device_id |
string|null | GOWA/device instance ID (Device channels only). |
channel_type |
string | cloud or device. |
direction |
string | inbound or outbound. |
status |
string | sent, delivered, read, failed, or unknown. |
message_type |
string | text, image, video, audio, document, location, contact, sticker. |
text |
string|null | Message body or media caption. |
media_url |
string|null | Public URL to download media when message_type is not text. |
sender_phone |
string|null | E.164-style phone number of the contact. |
contact |
object|null | id, name, phone — contact profile when available. |
sent_at |
ISO 8601 | When the message was sent/received. |
timestamp |
ISO 8601 | Same as sent_at. |
updated_at |
ISO 8601 | Last update time (e.g. status change). |
Device connection payload
Used by device.connected, device.disconnected, and device.status_changed. The device.status_changed event fires on every connection-status transition for any channel type (including Cloud/Coexistence) and carries from_status / to_status; device.connected / device.disconnected remain Device/QR-only for backward compatibility.
| Field | Type | Description |
|---|---|---|
account_id |
integer | WhatsApp account ID. |
device_id |
string|null | GOWA/device instance identifier (null for Cloud channels). |
channel_type |
string | device, cloud, or coexistence. |
status |
string | New connection status constant. |
from_status |
string|null | Previous connection status (device.status_changed). |
to_status |
string | New connection status — same as status (device.status_changed). |
phone_number |
string|null | Linked WhatsApp phone number. |
business_name |
string|null | Display name of the channel. |
HTTP headers
| Header | Description |
|---|---|
Content-Type | application/json |
User-Agent | Washeej-Webhook/1.0 |
X-Washeej-Event | Same as body event field |
X-Washeej-Signature | sha256=<HMAC-SHA256 hex> of the raw JSON body |
X-Washeej-Webhook-Id | Your webhook endpoint ID |
X-Washeej-Delivery-Id | Unique delivery log ID for debugging and resend |
X-Washeej-Timestamp | Unix timestamp when the delivery was sent |
Deprecated header
Older docs referenced
X-Webhook-Signature. Use X-Washeej-Signature for all new integrations.
Signature verification
- Read the raw request body as a string (before JSON parsing).
- Compute HMAC-SHA256 of the body using your webhook secret.
- Compare with the header value after removing the
sha256=prefix. - Use timing-safe comparison (e.g.
hash_equalsin PHP).
PHP
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WASHEEJ_SIGNATURE'] ?? '';
$secret = 'whsec_your_secret';
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$data = json_decode($payload, true);
// Process $data['event'] and $data['data']
Respond with HTTP 2xx within the timeout window. Non-2xx responses trigger retries.