Webhooks: not built yet

Dium does not send webhooks to your server today. Here is what works now, and the design we are proposing.

Last updated

What exists today

Dium does not send outbound webhooks to partner servers today. Here is what you can use instead.

MechanismDirectionWho can use it
Echo real-time roomsDium to browsersThe Dium app, including the app you mount on your site
60-second pollingBrowser to DiumThe Dium app, as a backup for Echo
Moat profile callback at /user/update.phpMoat to DiumMoat only. Not open to partners
Partner APIYour server to DiumYou, with a key. See the API reference

Echo rooms

Echo is the service that pushes live updates to the Dium app in the browser. It tries a WebSocket first, then long polling, then short polling every 3 seconds, then a CDN poll. On reconnect it resumes from the last message it saw.

RoomCarries
wave_{{id}}New replies and changes on a Wave
live_{{waveId}}Typing and presence during a live session
dm_{{userId}}New direct messages for a member
asq:page:{{exhibitId}}The wall on a sponsor or exhibitor Page
flow_{{ladderId}}Flow-wide announcements

If you mount Dium on your site, your members get these updates with no extra work. Echo is not a public event stream for your server, and room names are not a way to authorise anyone.

Polling

Even with Echo, the app checks for unread notifications and DMs every 60 seconds, and checks for new replies on the Wave a member is reading. If your backend needs to know about new content today, poll the API on a schedule and keep within the rate limits.

The Moat profile callback

When a member edits their Moat profile, Moat sends a form-encoded POST to /user/update.php with an X-API-Key header. Dium checks the key in constant time, finds the member by public_key (then email), updates the name and avatar, and merges the rest into the stored profile. Email changes are logged and not applied. The response is {"success", "user_id", "fields_updated"}.

This is an inbound callback from one trusted service. It is not a webhook you can subscribe to.

Proposal: outbound webhooks

Template, not live data. Everything from here down is a proposal, not a shipped feature and not a promise of a date. We are sharing it so partners can tell us which events they need. Write to [email protected].

If we build webhooks, we plan to follow the open Standard Webhooks spec so you can reuse existing verification libraries.

Headers

HeaderValue
webhook-idUnique message ID. Use it to drop duplicates.
webhook-timestampUnix time in seconds when the message was signed.
webhook-signatureSpace-separated list such as v1,<base64>. More than one entry during secret rotation.

Signing

HMAC-SHA256 over webhook-id + "." + webhook-timestamp + "." + raw body, keyed with your endpoint secret (whsec_ plus base64). Verify against the raw request body, before any JSON parsing. Compare in constant time.

Proposed verification, Node
// PROPOSAL: how verification would work under the Standard Webhooks spec.
// Dium does not send webhooks today.
import crypto from "node:crypto";

export function verifyDiumWebhook(rawBody, headers, secret) {
  const id = headers["webhook-id"];
  const ts = headers["webhook-timestamp"];
  const sigs = headers["webhook-signature"];
  if (!id || !ts || !sigs) return false;

  // Reject old or future messages. Never set the tolerance to 0.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = crypto.createHmac("sha256", key)
    .update(`${id}.${ts}.${rawBody}`)          // raw body, byte for byte
    .digest("base64");

  return sigs.split(" ").some((entry) => {
    const [version, sig] = entry.split(",");
    if (version !== "v1" || !sig) return false; // ignore unknown schemes
    const a = Buffer.from(sig), b = Buffer.from(expected);
    return a.length === b.length && crypto.timingSafeEqual(a, b);
  });
}

Payload

Proposed payload shape
{
  "type": "answer.best_marked",
  "timestamp": "2026-10-14T14:05:00Z",
  "data": { "flow_id": 42, "wave_id": 901, "answer_id": 5531 }
}

Candidate events

Based on the notifications Dium already creates. Tell us which ones matter to you.

Proposed eventWould fire when
wave.createdA new Wave is posted in a Flow
answer.createdSomeone replies to a Wave
answer.best_markedA best answer is marked and the Wave becomes solved
page.createdA sponsor, exhibitor, speaker or partner Page is created
member.join_requestedSomeone asks to join a Flow that needs approval
flow.suspendedA Flow is suspended or restored

Delivery, retries and ordering

  • Your endpoint answers with any 2xx within 10 seconds. Anything else counts as a failure, including redirects.
  • Failed messages are retried with growing gaps over several days, each retry with a fresh timestamp and signature.
  • Delivery is at least once, so the same webhook-id can arrive twice. Store IDs you have handled.
  • Order is not guaranteed. If order matters, fetch the current record from the API.