Skip to content
Get an API key

Get started

Webhooks

Nodium calls you when something happens. Signed, retried, logged.

What you receive

JSON

{ "id": "evt_5f1c0a9e2b7d4c3a8e6f1b2c3d4e5f60", "event": "message.received", "workspace_id": "…", "occurred_at": "2026-09-18T09:12:44.120Z", "data": { "message_id": "…", "conversation_id": "…", "contact_id": "…", "channel_type": "whatsapp", "type": "text", "text": "Hello, is my order ready?" }}
  • id is the event's own id: it stays the same when a delivery is retried or redelivered. Deduplicate on it.
  • Headers: X-Nodium-Event (the event name), X-Nodium-Delivery (this delivery), X-Nodium-Signature.
  • Answer with any 2xx within 10 seconds. Do the work afterwards.

Events

Verify the signature

X-Nodium-Signature: t=<seconds>,v1=<hex>. The signature is HMAC-SHA256(secret, "<t>.<raw body>"). During a secret rotation the header carries two v1= values — accept the call if any matches. Reject calls older than 5 minutes.

Node.js

import crypto from 'node:crypto'// rawBody: the request body exactly as received, before any JSON parsing.export function verifyNodium(rawBody, header, secret) { const parts = header.split(',') const t = parts.find(p => p.startsWith('t='))?.slice(2) const signatures = 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 signatures.some(sig => sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)))}

Python

import hmac, hashlib, timedef verify_nodium(raw_body: bytes, header: str, secret: str) -> bool: parts = header.split(",") t = next((p[2:] for p in parts if p.startswith("t=")), None) signatures = [p[3:] for p in parts if p.startswith("v1=")] if not t or abs(time.time() - int(t)) > 300: return False expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest() return any(hmac.compare_digest(sig, expected) for sig in signatures)

Retries, log, redelivery

  • A call that fails or does not answer 2xx is retried after 1 min, 5 min, 30 min, 2 h and 12 h — six attempts in all — then marked failed.
  • GET /webhooks/deliveries lists every delivery with the status your server answered and the error. That settles "we never received it".
  • POST /webhooks/deliveries/{id}/redeliver sends it again, same payload, same id.
  • POST /webhooks/{id}/rotate-secret issues a new secret; the old one keeps signing for 24 hours so you can deploy without missing a call.
  • POST /webhooks/{id}/test fires one signed test call now and tells you what came back.