Webhooks

Receive real-time events when message status changes. All deliveries are signed with HMAC-SHA256.

Events

EventDescription
message.sentMessage sent to WhatsApp server
message.deliveredMessage delivered to recipient's phone
message.readRecipient opened the chat
message.failedMessage failed to send
message.receivedIncoming message from a user

Signature verification

Every webhook delivery includes an X-Wayul-Signature header:

X-Wayul-Signature: t=<unix-timestamp>,v1=<hmac-sha256>

The HMAC is computed over body + "." + timestamp using your webhook secret. Verify the timestamp is within ±5 minutes tolerance.

Node.js verification example

import { createHmac, timingSafeEqual } from 'node:crypto';

function verifyWebhook(secret, body, headerValue) {
  const match = headerValue.match(/^t=(\d+),v1=([0-9a-f]+)$/);
  if (!match) return false;

  const ts = Number(match[1]);
  const v1 = match[2];

  // Check timestamp tolerance (±5 minutes)
  if (Math.abs(Date.now() - ts) > 5 * 60 * 1000) return false;

  // Compute expected signature
  const expected = createHmac('sha256', secret)
    .update(`${body}.${ts}`)
    .digest('hex');

  return timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

Retry policy

Failed deliveries are retried automatically up to 5 times with exponential backoff. Each delivery has a unique delivery_id for idempotency — your handler should process each delivery_id only once.

Payload format

{
  "event": "message.delivered",
  "delivery_id": "dlv_abc123",
  "message_id": "msg_9f8ec2a1",
  "device_id": "dev_01",
  "to": "628123456789",
  "timestamp": 1723273458421,
  "data": {
    "status": "delivered",
    "content": "Kode OTP Anda: 482913"
  }
}