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.
{
"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 | Device channels only: gowa_device_id when set, otherwise device_instance_id (the Device API instance_id). Null for Cloud channels. |
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 | Authenticated GET /v1/messages/{id}/media URL when the message has media. Requires client-id and client-secret. Not a public statics/media path. |
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). |
failure_code |
integer|null | Provider error code when the message failed (e.g. a Meta error code). |
failure_reason |
string|null | Human-readable failure reason when the message failed. |
billing_blocked |
boolean | true when failure_code is a Meta billing / payment eligibility error. |
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_id when set, otherwise device_instance_id (the Device API instance_id). 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. Deprecated on 2027-04-06 — verify X-Washeej-Signature-V2 instead. |
X-Washeej-Signature-V2 | sha256=<HMAC-SHA256 hex> of X-Washeej-Timestamp + . + raw JSON body (recommended) |
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 (seconds) when the delivery was sent; part of the V2 signature |
X-Webhook-Signature. Use X-Washeej-Signature for all new integrations.
Signature verification
Verify X-Washeej-Signature-V2 (recommended). The body-only X-Washeej-Signature is deprecated on 2027-04-06.
- Read the raw request body as a string (before JSON parsing).
- Read
X-Washeej-Timestampand reject deliveries that are too old (replay protection). - Compute HMAC-SHA256 of
timestamp + "." + bodyusing your webhook secret, prefixed withsha256=. - Compare with
X-Washeej-Signature-V2using a timing-safe comparison (e.g.hash_equalsin PHP).
$payload = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_WASHEEJ_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_WASHEEJ_SIGNATURE_V2'] ?? '';
$secret = 'whsec_your_secret';
// Reject stale deliveries (replay protection), e.g. older than 5 minutes.
if (abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit('Stale delivery');
}
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $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']
Legacy body-only signature (deprecated on 2027-04-06):
$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.