Webhooks
Register a webhook endpoint to receive real-time events for auth, delivery, and replies.
Webhooks let you receive events from Mersal in real time, instead of polling the GET /api/get/{channel}/{id?} lookup endpoints for status. Register an endpoint on your own server once, and Mersal will push events to it as they happen.
How delivery status flows back into Mersal
Behind the scenes, Mersal receives delivery and status updates from several sources: inbound provider webhooks from email providers (SendGrid, Amazon SES, Mailgun, Mailjet) for bounce and delivery events, the WhatsApp Cloud API webhook, and Mersal's Node-based WhatsApp gateway webhook for QR-connected devices. These internal webhooks are how Mersal itself learns that a message was delivered, bounced, or replied to — you don't need to configure or consume them directly.
What you, as an integrator, care about is the customer-facing webhook described below: the one Mersal calls out to on your server once it has processed those internal events.
Registering a webhook endpoint
Webhook registration is done from the dashboard rather than via API call. Go to Automation → Webhooks and add your endpoint URL. From there you can:
- Register an endpoint URL that Mersal will call when subscribed events occur.
- Choose event types to subscribe to — auth events, delivery events, and reply events.
- Send a test ping to your endpoint to confirm it's reachable and responding correctly before relying on it in production.
- Rotate your webhook secret if you suspect it's been exposed, without needing to re-register your endpoint URL.
Event types
| Event category | Description |
|---|---|
| Auth | Events related to authentication/OTP flows (e.g. verification codes sent through Mersal). |
| Delivery | Delivery status updates for sent messages (queued, delivered, failed, etc.) across SMS, WhatsApp, and email. |
| Reply | Incoming replies from recipients, useful for two-way conversations and automation triggers. |
Use the test ping action after registering your endpoint to verify your server returns a successful response before you start relying on live traffic.
Verify the webhook secret
Always validate incoming webhook requests against your webhook secret before trusting the payload. If you rotate the secret, update it on your receiving server at the same time — old signatures will no longer validate.
Example: a minimal receiver
<?php
// webhook.php — Mersal event receiver
$payload = json_decode(file_get_contents('php://input'), true);
// Validate the secret before trusting the payload
$secret = getenv('MERSAL_WEBHOOK_SECRET');
$signature = $_SERVER['HTTP_X_WEBHOOK_SECRET'] ?? '';
if (!hash_equals($secret, $signature)) {
http_response_code(401);
exit;
}
switch ($payload['event'] ?? '') {
case 'message.delivered':
// Update the message status in your database
break;
case 'message.failed':
// Log the failure or retry on another channel
break;
case 'message.reply':
// Store the reply or route it to your support team
break;
}
http_response_code(200); // Important: reply 200 so Mersal doesn't retry
echo 'ok';import express from "express";
const app = express();
app.use(express.json());
app.post("/mersal/webhook", (req, res) => {
// Validate the secret before trusting the payload
if (req.headers["x-webhook-secret"] !== process.env.MERSAL_WEBHOOK_SECRET) {
return res.sendStatus(401);
}
const { event, number, channel } = req.body;
switch (event) {
case "message.delivered":
// Update the message status in your database
break;
case "message.failed":
// Log the failure or retry on another channel
break;
case "message.reply":
// Store the reply or route it to your support team
break;
}
res.sendStatus(200); // Important: reply 200 so Mersal doesn't retry
});
app.listen(3000);import hmac
import os
from flask import Flask, request
app = Flask(__name__)
@app.post("/mersal/webhook")
def mersal_webhook():
# Validate the secret before trusting the payload
signature = request.headers.get("X-Webhook-Secret", "")
if not hmac.compare_digest(signature, os.environ["MERSAL_WEBHOOK_SECRET"]):
return "", 401
payload = request.get_json(silent=True) or {}
event = payload.get("event")
if event == "message.delivered":
pass # Update the message status in your database
elif event == "message.failed":
pass # Log the failure or retry on another channel
elif event == "message.reply":
pass # Store the reply or route it to your support team
return "ok", 200 # Important: reply 200 so Mersal doesn't retryErrors
Webhook management itself is done through the dashboard UI, not a documented API endpoint. For errors on the sending/read endpoints that feed the events behind your webhooks, see Errors and Status Codes.
