Webhooks

Webhooks

Mawjly POSTs HTTP webhooks to your endpoint when interesting things happen on a channel — useful for keeping your database in sync, triggering side effects, or building custom analytics.

Configure

Dashboard → your app → Webhooks → Add webhook. Pick the events you want, paste your endpoint URL, save. Mawjly returns the signing secret — copy it now, it won’t be shown again. Use it to verify each request.

Event types

EventFires when
channel_occupiedA previously empty channel gains its first subscriber
channel_vacatedThe last subscriber leaves a channel
member_addedA new member joins a presence channel (first connection from that user_id)
member_removedThe last connection for a presence member disconnects
client_eventA subscriber publishes a client- event on a private/presence channel
subscription_count_changedSubscriber count on a channel changes (delivered with throttling)

Payload shape

A single POST may batch multiple events:

{
  "time_ms": 1748812345000,
  "events": [
    { "name": "channel_occupied", "channel": "presence-room-42" },
    { "name": "member_added", "channel": "presence-room-42", "user_id": "alice" },
    {
      "name": "client_event",
      "channel": "private-x",
      "event": "client-typing",
      "data": "{\"user\":\"alice\"}",
      "socket_id": "123.456789"
    }
  ]
}

Headers

Content-Type: application/json
X-Pusher-Key: YOUR_APP_KEY
X-Pusher-Signature: <hex_hmac_sha256_of_body>

The signature is computed over the raw request body using your app’s secret (not the per-webhook signing secret — that one is for older versions; current Pusher protocol signs with the app secret).

Verify in PHP

$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, $appSecret);
$received = $_SERVER['HTTP_X_PUSHER_SIGNATURE'] ?? '';
 
if (! hash_equals($expected, $received)) {
    http_response_code(401);
    exit;
}
 
$payload = json_decode($body, true);
foreach ($payload['events'] as $event) {
    // process
}

Verify in Node.js

import { createHmac, timingSafeEqual } from 'node:crypto';
 
app.post('/webhooks/mawjly', express.raw({ type: 'application/json' }), (req, res) => {
  const body: Buffer = req.body;
  const sig = req.header('X-Pusher-Signature') ?? '';
  const expected = createHmac('sha256', process.env.MAWJLY_SECRET!).update(body).digest('hex');
 
  if (!timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return res.sendStatus(401);
  }
 
  const { events } = JSON.parse(body.toString('utf8'));
  for (const event of events) {
    // process
  }
  res.sendStatus(200);
});

Reliability

  • Webhooks are delivered at-least-once. Make your handler idempotent.
  • Mawjly retries non-2xx responses with exponential backoff.
  • Each delivery’s status, response code, and duration are recorded — visible from Webhooks tab → … menu → View deliveries.

Replay a failed delivery

In the dashboard’s webhook delivery log, click any failed row to re-fire the same payload. Useful for testing your handler after a fix without waiting for new events.