Signed HMAC-SHA256 Events

Delivery Status Webhooks

Receive automatic message-status updates at your HTTPS endpoint.

A webhook lets WebWMS2U send an HTTPS POST request to your system whenever an API message changes status. This avoids repeatedly polling the Message Status API.

1. Create a receiver

Publish an HTTPS PHP endpoint on your server.

2. Configure WebWMS2U

Choose the API key, enter the endpoint URL and securely copy its signing secret.

3. Verify and process

Verify every signature, save the event, then return HTTP 200.

Configure Your Webhook

Requirements

Request headers

HeaderPurpose
X-WebWMS2U-TimestampUnix timestamp used for replay protection and signature calculation.
X-WebWMS2U-Signaturev1= followed by the HMAC-SHA256 signature.
X-WebWMS2U-Event-IDUnique event identifier for duplicate protection.
Content-Typeapplication/json

Event example

{
  "id": "01fc766e-e29f-b401-1f91-bdeb3bf1b427",
  "type": "message.submitted",
  "created_at": "2026-08-11T09:56:57Z",
  "data": {
    "message_id": 128047,
    "provider_message_id": "3EB06BCB611F4044A512F7",
    "status": "submitted",
    "channel": "personal",
    "to": "60123456789",
    "message_type": "text",
    "credits_charged": 1,
    "credits_refunded": 0,
    "error_code": null
  }
}

The public channel name is personal. Older webhook installations may return the legacy internal value baileys; integrations may temporarily treat both values as the personal-device channel.

PHP receiver example

<?php
$webhookSecret = 'YOUR_WEBHOOK_SIGNING_SECRET';
$rawBody = (string) file_get_contents('php://input');
$timestamp = (string) ($_SERVER['HTTP_X_WEBWMS2U_TIMESTAMP'] ?? '');
$signature = (string) ($_SERVER['HTTP_X_WEBWMS2U_SIGNATURE'] ?? '');

if ($timestamp === '' || !ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
    http_response_code(401);
    exit('Invalid timestamp');
}

$expected = 'v1=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $webhookSecret);
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

$event = json_decode($rawBody, true);
if (!is_array($event) || empty($event['id']) || empty($event['type'])) {
    http_response_code(400);
    exit('Invalid event');
}

$messageId = $event['data']['message_id'] ?? null;
$status = $event['data']['status'] ?? null;

/*
 * The webhook has been verified and is safe to process.
 *
 * Add your own code below to handle the status update. For example:
 * - Save the event to your database.
 * - Append the event to a protected local log file.
 * - Update the matching CRM, order, booking or notification record.
 *
 * Store $event['id'] uniquely to prevent duplicate processing because
 * WebWMS2U may retry the same event if it does not receive HTTP 200.
 */

// Your processing code goes here.

http_response_code(200);
header('Content-Type: application/json');
echo json_encode(['received' => true]);

Quick testing: save to a log

For initial testing, place this after verification and before returning HTTP 200:

$saved = file_put_contents(
    __DIR__ . '/webwms2u-webhooks.log',
    json_encode($event, JSON_UNESCAPED_SLASHES) . PHP_EOL,
    FILE_APPEND | LOCK_EX
);
if ($saved === false) {
    http_response_code(500);
    exit('Unable to save webhook');
}
Protect the log: Prefer storing it outside public_html. If it must be web-accessible, deny browser access through your server configuration.

Production database guidance

Store the event ID in a column with a UNIQUE index. Insert the event before performing irreversible work. If the ID already exists, return HTTP 200 without repeating the work.

Events

Depending on the channel and available receipt information, WebWMS2U may send message.submitted, message.sent, message.delivered, message.read, message.failed or message.blocked.

Retries and responses

Inactive endpoints

Setting an endpoint to Inactive stops webhook delivery for that configuration. It does not disable message sending, the API key or authenticated status retrieval.