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"
  }
}
FieldTypeDescription
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.

FieldTypeDescription
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

HeaderDescription
Content-Typeapplication/json
User-AgentWasheej-Webhook/1.0
X-Washeej-EventSame as body event field
X-Washeej-Signaturesha256=<HMAC-SHA256 hex> of the raw JSON body. Deprecated on 2027-04-06 — verify X-Washeej-Signature-V2 instead.
X-Washeej-Signature-V2sha256=<HMAC-SHA256 hex> of X-Washeej-Timestamp + . + raw JSON body (recommended)
X-Washeej-Webhook-IdYour webhook endpoint ID
X-Washeej-Delivery-IdUnique delivery log ID for debugging and resend
X-Washeej-TimestampUnix timestamp (seconds) when the delivery was sent; part of the V2 signature
Deprecated header Older docs referenced 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.

  1. Read the raw request body as a string (before JSON parsing).
  2. Read X-Washeej-Timestamp and reject deliveries that are too old (replay protection).
  3. Compute HMAC-SHA256 of timestamp + "." + body using your webhook secret, prefixed with sha256=.
  4. Compare with X-Washeej-Signature-V2 using a timing-safe comparison (e.g. hash_equals in PHP).
PHP — V2 (recommended)
$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):

PHP — legacy X-Washeej-Signature
$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.