Webhooks,
signed and on time.
Murmurly posts each update to your URL as it happens. It never pulls anything back.
“Connect an endpoint, check my signature, answer 2xx. That’s it.”
01
Connect an endpoint
Admins · Route signals to Webhook-
Open Webhooks
Settings → Integrations & APIs → Webhooks.
-
Add your URL
Give it a name and an https URL, then Connect endpoint. Private network addresses are refused.
-
Copy the secret
It is shown once. Store it on your server, never in code.
-
Route signals
Under Signal Routing, switch on what this endpoint should receive.
02
Verify every delivery
Same scheme as Stripe
X-Murmurly-Signature: t=<unix>,v1=<hex>
HMAC-SHA256 of <t>.<raw body> with your secret. Accept if any v1 matches, and reject anything older than 300 seconds.
import crypto from 'node:crypto'
// Use the raw request body, exactly as received.
export function verify(rawBody, header, secret) {
const parts = header.split(',')
const t = parts.find((p) => p.startsWith('t='))?.slice(2)
const sigs = parts.filter((p) => p.startsWith('v1=')).map((p) => p.slice(3))
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
return sigs.some((s) => s.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)))
} 03
What arrives
POST https://hooks.example.com/murmurly
X-Murmurly-Signal: task.assigned
X-Murmurly-Delivery: <delivery id>
X-Murmurly-Signature: t=1792420000,v1=5f2b…
{
"id": "<delivery id>",
"signal": "task.assigned",
"version": …,
"workspaceId": "…",
"channel": "…",
"sentAt": "2026-10-19T14:40:00Z",
"recipient": { … },
"payload": { … }
} -
idThe delivery id. Deliveries are at least once, so use it to skip retries you already handled. -
signalWhich update this is, for example task.assigned. -
versionThe payload version for this signal. -
workspaceIdThe workspace it came from. -
sentAtWhen Murmurly sent it. -
recipientOnly on signals addressed to one person. -
payloadThe update itself.
04
Signals
Switch them on under Signal RoutingEngagements
-
event.created -
event.requested -
event.status_changed -
event.description_changed -
event.cancelled -
event.assignee_added
Guests and RSVPs
-
event_person.invitedEssential -
event_person.claimed -
event_person.removed -
event_rsvp.answered -
event_rsvp.accepted -
event_rsvp.declined -
event_rsvp.confirmed
Tasks
-
task.assignedEssential
Conversations
-
thread.replied -
thread.answered -
thread.mentioned -
thread.external -
note.mentioned
People and organizations
-
person.assignee_added -
organization.assignee_added
Fields and proposals
-
field.date_reached -
field.member_linked -
field.retention_review_due -
proposal.decided
05
Limits and retries
2xx in 10 s Anything else, a redirect included, counts as failed.
Retried Failed deliveries are retried with backoff. Same id each time.
120 burst Then 600 an hour, per endpoint. Over it waits, never drops.
300 s Reject signatures older than this.
24 h overlap Rotate secret keeps the old key signing for 24 hours.
https only localhost, .local, .internal and private IPs are refused.
Need to send data in, too? The API takes requests from your systems.