Lightweight WhatsApp Cloud API webhook parser. Typed notifications for messages, statuses, and errors. HMAC signature verification. Zero dependencies.
Author: Renzo Johnson
- PHP 8.4 or higher (uses
readonlyclasses and enums) ext-json- No other extensions or libraries required (zero dependencies)
composer require renzojohnson/whatsapp-webhookuse RenzoJohnson\WhatsAppWebhook\WebhookHandler;
use RenzoJohnson\WhatsAppWebhook\MessageType;
use RenzoJohnson\WhatsAppWebhook\Notification\MessageNotification;
$handler = new WebhookHandler('your-app-secret');
// Register listeners
$handler->onMessage(MessageType::Text, function (MessageNotification $msg): void {
echo "Message from {$msg->from}: {$msg->text()}";
});
// Verify signature and handle
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
if (!$handler->verifySignature($rawBody, $signature)) {
http_response_code(401);
exit;
}
$handler->handle($rawBody);
http_response_code(200);use RenzoJohnson\WhatsAppWebhook\WebhookHandler;
use RenzoJohnson\WhatsAppWebhook\WebhookException;
$handler = new WebhookHandler('your-app-secret');
// Step 1: Handle Meta's verification challenge (GET)
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$challenge = $handler->handleVerification($_GET, 'your-verify-token');
if ($challenge !== null) {
echo $challenge;
exit;
}
http_response_code(403);
exit;
}
// Step 2: Verify signature (POST)
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
if (!$handler->verifySignature($rawBody, $signature)) {
http_response_code(401);
exit;
}
// Step 3: Parse and handle
try {
$notifications = $handler->handle($rawBody);
http_response_code(200);
} catch (WebhookException $e) {
http_response_code(400);
}| Type | Enum | Description |
|---|---|---|
text |
MessageType::Text |
Plain text message |
image |
MessageType::Image |
Image with optional caption |
video |
MessageType::Video |
Video with optional caption |
audio |
MessageType::Audio |
Audio message |
document |
MessageType::Document |
Document with filename |
sticker |
MessageType::Sticker |
Sticker |
location |
MessageType::Location |
GPS coordinates with name/address |
contacts |
MessageType::Contacts |
Contact cards |
button |
MessageType::Button |
Quick reply button tap |
interactive |
MessageType::Interactive |
Button reply or list selection |
reaction |
MessageType::Reaction |
Emoji reaction |
order |
MessageType::Order |
Product order |
use RenzoJohnson\WhatsAppWebhook\MessageType;
use RenzoJohnson\WhatsAppWebhook\Notification\MessageNotification;
$handler->onMessage(MessageType::Text, function (MessageNotification $msg): void {
echo $msg->text();
});
$handler->onMessage(MessageType::Image, function (MessageNotification $msg): void {
echo "Image: {$msg->mediaId()} ({$msg->mediaMimeType()})";
echo "Caption: {$msg->caption()}";
});
$handler->onMessage(MessageType::Location, function (MessageNotification $msg): void {
echo "Location: {$msg->latitude()}, {$msg->longitude()}";
echo "Name: {$msg->locationName()}";
});
$handler->onMessage(MessageType::Interactive, function (MessageNotification $msg): void {
if ($msg->interactiveType() === 'button_reply') {
echo "Button: {$msg->interactiveButtonTitle()} (ID: {$msg->interactiveButtonId()})";
}
if ($msg->interactiveType() === 'list_reply') {
echo "List: {$msg->interactiveListTitle()}";
}
});
$handler->onMessage(MessageType::Reaction, function (MessageNotification $msg): void {
echo "Reacted with {$msg->reactionEmoji()} to {$msg->reactionMessageId()}";
});$handler->onAnyMessage(function (MessageNotification $msg): void {
error_log($msg->getSummary());
});use RenzoJohnson\WhatsAppWebhook\StatusType;
use RenzoJohnson\WhatsAppWebhook\Notification\StatusNotification;
$handler->onStatus(StatusType::Delivered, function (StatusNotification $status): void {
echo "Delivered to {$status->recipientId}";
});
$handler->onStatus(StatusType::Read, function (StatusNotification $status): void {
echo "Read by {$status->recipientId}";
});
$handler->onStatus(StatusType::Failed, function (StatusNotification $status): void {
if ($status->hasErrors()) {
echo "Error {$status->errorCode()}: {$status->errorTitle()}";
}
});
$handler->onAnyStatus(function (StatusNotification $status): void {
error_log($status->getSummary());
});$handler->onAnyStatus(function (StatusNotification $status): void {
if ($status->isBillable()) {
echo "Category: {$status->pricingCategory()}";
echo "Model: {$status->pricingModel()}";
}
if ($status->conversationId()) {
echo "Conversation: {$status->conversationId()}";
echo "Origin: {$status->conversationOrigin()}";
}
});$handler->onAnyMessage(function (MessageNotification $msg): void {
if ($msg->isForwarded()) {
echo "This message was forwarded";
}
if ($msg->isFrequentlyForwarded()) {
echo "This message has been forwarded many times";
}
if ($msg->contextMessageId()) {
echo "Reply to: {$msg->contextMessageId()}";
}
});$notifications = $handler->parse($rawBody);
foreach ($notifications as $notification) {
if ($notification instanceof MessageNotification) {
echo $notification->getSummary();
}
}use RenzoJohnson\WhatsAppWebhook\WebhookException;
try {
$handler->parse($rawBody);
} catch (WebhookException $e) {
// Invalid JSON, wrong object type, empty entry
}- whatsapp-cloud-api — Send WhatsApp messages via Cloud API
MIT License. Copyright (c) 2026 Renzo Johnson.