← All posts
Engineering7 min read

[ENGINEERING] · May 7, 2026 · 08:30

Webhooks 101: build your own notifications, CRM sync, and AI handlers

Every chat event in muro is delivered to your URL with HMAC-SHA256 signing and exponential retries. Here's how to build receivers that actually behave.

Tm

The muro team

muro.chat

#webhooks#engineering#integrations#hmac#retry
Webhooks 101: build your own notifications, CRM sync, and AI handlers

muro POSTs every event to your registered URL. You verify the signature, do something useful, return 2xx. We retry with exponential backoff if you don't (30s · 1m · 5m · 30m · 2h · 6h, then we give up). This article is the contract + a sample receiver you can copy.

The contract

  • Method: POST (always)
  • Body: JSON. Top-level fields: id, type, organization_id, created_at, data.
  • Headers: x-muro-event (event type), x-muro-event-id (idempotency key), x-muro-timestamp (unix seconds), x-muro-signature (HMAC).
  • Timeout: 10 s. Anything longer is treated as a failure.
  • Success: any 2xx. We don't parse your response body; just status code.

Verifying the signature

Every request carries x-muro-signature: t=<ts>,v1=<hex>. The signature is HMAC-SHA256(secret, "<ts>.<rawBody>"). Always use a constant-time compare (timingSafeEqual in Node, hmac.compare_digest in Python) — naive === leaks timing information that an attacker can exploit.

typescriptimport crypto from 'node:crypto';
import express from 'express';

const app = express();
const SECRET = process.env.MURO_WEBHOOK_SECRET!;

app.post('/muro/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.header('x-muro-signature') ?? '';
  const m = /^t=(\d+),v1=([0-9a-f]+)$/.exec(header);
  if (!m) return res.status(401).end();

  const [, ts, sig] = m;
  const expected = crypto
    .createHmac('sha256', SECRET)
    .update(`${ts}.${req.body.toString('utf8')}`)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex'))) {
    return res.status(401).end();
  }

  // Optional: reject events older than 5 minutes (replay protection)
  if (Math.abs(Date.now()/1000 - Number(ts)) > 300) return res.status(401).end();

  const event = JSON.parse(req.body.toString('utf8'));
  // Process event.type, event.data — return 2xx fast and do work async if heavy
  res.json({ ok: true });
});

Idempotency

Retries mean the same event will arrive multiple times if your first 2xx didn't make it back to us. Treat `x-muro-event-id` as the dedup key. Store it for ~24h in Redis or a seen_events table; ignore duplicates.

Common patterns we see

Team-chat notification on every conversation

Subscribe to conversation.opened. POST a message to your team chat's incoming-webhook URL with the visitor's first message + a link back to the muro inbox. ~15 lines.

CRM sync on visitor identification

Subscribe to visitor.identified. When fired, the visitor handed over their email — push it to your CRM with the conversation transcript as a note.

AI summariser

Subscribe to conversation.closed. Send the message log to your LLM of choice with a prompt like "summarise the resolution in 50 words" and store the result in your data warehouse for analytics.

On-call paging for VIPs

Subscribe to message.created. If the visitor email is in your VIP list and FRT > 2 minutes, page the on-call engineer via your on-call tool. Saves angry CEOs from churning.

Testing receivers without deploying

In /app/settings/webhooks we have a "Send test event" button that POSTs a synthetic message.created to your endpoint. Pair it with ngrok for local dev or serveo.net if you're behind a strict firewall. The test event has data.test: true so you can branch your logic.

✦ ✦ ✦

Good webhook receivers are 50% verifying the signature, 50% not blocking. Get those two right and you can build basically any integration on top of muro in an afternoon.

✦ Try it

One support inbox for all your projects, one flat price.

muro is live chat, an AI that answers from your own docs, and a shared inbox for every site you run. See it both sides in the live demo, check the flat pricing, or see how muro compares.

Tm

✎ Written by

The muro team

muro.chat