OverviewPlatformsAPI Reference

Webhooks

Get mentions and digests as signed requests to your own URL. Setup, retries, signature checks and every event.


A webhook is a channel that sends to a URL you control. Every alert rule attached to it sends one signed JSON POST for each matching mention (instant rules) or for each digest (hourly, daily and weekly rules).

  • One URL can serve many rules. Use the payload's event to tell them apart.
  • A delivery tells you something happened. The mention itself is always at GET /v1/mentions/{id}.
  • Reply with a 2xx right away and do the work afterwards. Rumoro ignores the response body.

Setup

  1. Create a webhook channel. The response contains the signing secret, which you only see once.
  2. Add alert rules that send to it, each with its own filter and event name.
  3. Rumoro sends a POST for every matching mention or digest. Reply with a 2xx within 10 seconds.

Create the channel

const { data: channel } = await rumoro.createChannel({
  body: { kind: 'webhook', url: 'https://hooks.example.dev/rumoro', label: 'Support bot', headers: { 'X-Team': 'support' } },
});
{
  "id": "dest_...",
  "label": "Support bot",
  "stats": { "alerts": 0, "activeAlerts": 0, "lastDeliveryAt": null, "last7d": { "total": 0, "failed": 0 } },
  "createdAt": "2026-10-04T09:12:44.208Z",
  "kind": "webhook",
  "config": {
    "url": "https://hooks.example.dev/rumoro",
    "headers": { "x-team": "support" },
    "events": [],
    "secret": "whsec_..."
  }
}

You get config.secret only in this response and after a rotation. Without a label, the URL's host is used. The URL must be public and use https.

Rumoro sends your headers with every request. You can set up to 20. Names are letters, digits and hyphens (stored lowercase), and values are up to 1,024 printable ASCII characters. Names that would clash with Rumoro's own signing or transport headers are refused with 400.

PATCH /v1/channels/{id} changes the URL, label or headers. Changing the URL or headers, or turning the channel off, cancels deliveries that haven't gone out yet.

Attach a rule

await rumoro.createAlert({
  body: { name: 'Bug reports', mode: 'instant', event: 'mention.bug_report', filter: { intents: ['bug_report'] }, channelIds: ['dest_...'] },
});

event can be any lowercase name with dots, up to 60 characters. Instant rules default to mention.matched and digest rules to digest.

Retries

When your URL doesn't reply with a 2xx within 10 seconds, Rumoro decides by the reason.

  • Temporary problems are a timeout, a network error, 408, 429 or a 5xx. Instant deliveries are tried up to 5 times, about 30 seconds apart, or later if your Retry-After asks for it. Every attempt has the same id.
  • Permanent problems are a redirect or any other 4xx. Rumoro doesn't retry and records the status in the delivery log.
  • Digests are retried per channel every 5 minutes for up to 6 hours. Hourly digests are retried until 45 minutes past the hour after they were due.
  • A payload larger than 1 MiB is never sent.

Duplicates

A retry has the same id as the first attempt, so keep the ids you have processed and skip repeats. A replay (POST /v1/deliveries/{id}/replay) comes with a new id.

Each delivery is one keyword match. A post that matches two of your keywords arrives twice, with two mention ids and two keyword objects. Mentions marked ignored or done, and posts by muted authors, are not sent. Matches scored as noise only reach rules whose minRelevance is below 40.

Signature verification

Every request carries these headers.

HeaderValue
Content-Typeapplication/json
X-Mentions-TimestampWhen Rumoro built the request, in Unix seconds
X-Mentions-Signature-V2v2= and the hex HMAC-SHA256 of the timestamp, a dot and the raw body
X-Mentions-SignatureThe hex HMAC-SHA256 of the raw body only. Kept for older code, without replay protection.

Verify a request

Use the whole secret as the HMAC key, including whsec_. Check the V2 signature against the raw body before you parse it, compare in constant time, and reject timestamps more than five minutes from now. Because the timestamp is part of the signature, a captured request can't be reused later. Every retry gets a new timestamp and signature.

import { createHmac, timingSafeEqual } from 'node:crypto';

// body: the raw request body as received. secret: your channel secret, including "whsec_".
export function isFromRumoro(body: Buffer, timestamp: string | undefined, signature: string | undefined, secret: string) {
  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!timestamp || !signature || !(age <= 300)) return false;
  const expected = Buffer.from('v2=' + createHmac('sha256', secret).update(timestamp + '.').update(body).digest('hex'));
  const given = Buffer.from(signature);
  return expected.length === given.length && timingSafeEqual(expected, given);
}
// isFromRumoro(rawBody, req.headers['x-mentions-timestamp'], req.headers['x-mentions-signature-v2'], secret)
import hashlib
import hmac
import time

# body: the raw request body as bytes. secret: your channel secret, including "whsec_".
def is_from_rumoro(body: bytes, timestamp: str | None, signature: str | None, secret: str) -> bool:
    if not timestamp or not signature or not timestamp.isdigit():
        return False
    if abs(time.time() - int(timestamp)) > 300:
        return False
    digest = hmac.new(secret.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest("v2=" + digest, signature)

Rotate the secret

POST /v1/channels/{id}/rotate-secret creates a new secret and shows it once. The old secret stops working at the same moment, so update your code right away.

The payload

All events use the same envelope.

{
  "id": "dlv_...",
  "event": "mention.bug_report",
  "createdAt": "2026-10-04T09:12:44.208Z",
  "alert": { "id": "feed_...", "name": "Bug reports" },
  "data": { }
}
FieldMeaning
idThe delivery id. In the delivery log, scheduled daily and hourly digests are listed under the rule and period instead (feed_…:2026-10-04, feed_…:h:2026-10-04T09).
eventThe rule's event name, an account event, or test
createdAtWhen Rumoro built the request
alertThe rule that sent it. alert.id is null for a channel test, and alert is null for account events.
dataA mention, a digest or an account event. For a test it is { "message" }.

Events

Rule events

The dashboard's Webhooks page offers these presets. Each one is a normal alert rule.

EventModeFilter
mention.matchedinstantNo filter, every relevant mention
mention.high_relevanceinstantminRelevance: 80
mention.negative, mention.positiveinstantsentiments: ["negative"] or ["positive"]
mention.buy_intent, mention.question, mention.complaint, mention.comparison, mention.churn_intent, mention.bug_report, mention.pricinginstantintents with that intent
review.negativeinstantratings: [1, 2]
digestdailyNo filter, one summary a day
teston requestSent by the test calls below

Account events

Account events don't use rules. A channel subscribes to them by name, with events on POST or PATCH /v1/channels.

EventSent when
keyword.cappedA keyword hit its monthly mention cap
keyword.paused_for_balanceA keyword stopped because the balance ran out
keyword.resumedA keyword is collecting again
wallet.lowThe balance dropped to 20% of the last credit
wallet.pausedAll keywords stopped for lack of balance
wallet.resumedA credit restarted them
mention.spikeA keyword got far more mentions than usual in the last hour
sentiment.negative_spikeA keyword's mentions turned negative over the last day
keyword.noisyMost of a keyword's matches are noise
channel.failingA channel's recent deliveries all failed

The last four come from Needs attention.

Testing and the delivery log

CallWhat it does
POST /v1/channels/{id}/testSends an event: "test" request to this channel and returns { "outcomes": [{ "channelId", "ok", "error" }] }
POST /v1/alerts/{id}/testDoes the same for every channel of the rule, with alert set
GET /v1/channels/{id}/deliveriesRecent deliveries. Each has kind (mention, digest or event), event, status (pending, delivered or failed), attempts, the last error, sentAt, the rule's name and a short mention summary. Tests are not listed.
POST /v1/deliveries/{id}/retrySends a failed or held delivery again with the same id
POST /v1/deliveries/{id}/replaySends a finished delivery again with a new id
DELETE /v1/channels/{id}Removes the channel from all rules and returns 204

Retry and replay need a body with a requestId (a UUID), such as { "requestId": "2f0b7c1e-4d93-4a8e-9b61-5c3e7a0d1f24" }, and return 202. They are not in the API reference. Conventions explains how these endpoints answer.

With the CLI, use rumoro channels:test dest_... and rumoro channels:deliveries dest_.... The other calls are in the API reference.

Was this page helpful?

On this page