Webhook delivery
Every notification BrandBanta fires (alerts, scan completions, weekly digests) can be delivered to a webhook. Slack is the most common destination; any generic JSON endpoint works.
What gets delivered
Webhooks fire for every notification kind, gated by your per-channel preferences in Settings → Notifications:
| Notification kind | When it fires |
|---|---|
mention_rate_drop | Your mention rate fell ≥10% with cross-platform corroboration |
competitor_surge | A competitor's mention rate rose ≥10% on ≥2 platforms |
sentiment_swing | Sentiment mix shifted toward negative ≥15 percentage points |
new_competitor_detected | An unseen brand is now appearing alongside yours |
tier1_source_gap | A high-DR domain cites competitors but not you for ≥5 queries |
weekly_digest | Weekly snapshot narrative delivered |
scan_completed | A specific scan you've subscribed to finished |
You enable each kind independently per channel (in-app / email / webhook).
Setting up a Slack webhook
- In Slack, go to your workspace → Apps → Incoming Webhooks
- Add a new webhook to the channel you want notifications in
- Copy the webhook URL (looks like
https://hooks.slack.com/services/T0.../B0.../...) - In BrandBanta, go to Settings → Notifications → Webhook delivery
- Paste the URL, label it "Slack #brand-tracking" (or whatever fits), and save
- Click Test webhook to send a sample payload
You should see a card appear in your Slack channel within a few seconds.
Setting up a generic JSON endpoint
If you want to pipe notifications into your own backend, Zapier, n8n, or any data warehouse:
- Build an endpoint that accepts
POST application/json - In Settings → Notifications → Webhook delivery, paste the URL with Format: generic JSON selected
- Save and test
Your endpoint receives the payload schema below.
Payload schema
{
"notification": {
"id": "ckq4...",
"type": "mention_rate_drop",
"organizationId": "org_abc...",
"createdAt": "2026-05-23T13:42:00.000Z",
"data": {
"headline": "Mention rate dropped 14% this week",
"title": "Visibility regression detected",
"message": "Your mention rate fell from 27% to 23% across 92 scans. Drop appears on ChatGPT and Perplexity; Claude and Gemini are flat.",
"details": {
"previousMentionRate": 0.27,
"currentMentionRate": 0.23,
"platformBreakdown": {
"openai": -0.06,
"anthropic": 0.01,
"googleai": -0.0,
"perplexity": -0.07
}
}
},
"link": "https://app.brandbanta.com/<org-handle>/insights"
},
"organization": {
"id": "org_abc...",
"name": "Get Vocal",
"slug": "get-vocal-abc..."
},
"deliveredAt": "2026-05-23T13:42:01.247Z"
}data.headline / data.title / data.message are present on every payload. data.details varies by notification kind — see the per-kind schema in Webhook schema reference for the full shape catalogue.
Slack-specific formatting
When the URL is a Slack incoming webhook (hooks.slack.com), we wrap the payload in Slack Block Kit so it renders as a card with:
- Title (clickable link to the dashboard)
- Headline in bold
- Message in regular text
- A footer with the workspace name + timestamp
You don't need to change anything on Slack's side beyond creating the incoming webhook.
Retries and delivery guarantees
- Webhooks deliver with at-least-once semantics
- HTTP 2xx response is treated as success
- Non-2xx responses are retried with exponential backoff (3 attempts over ~10 minutes)
- If all retries fail, the delivery is dropped — the in-app notification still appears, so you don't lose visibility into what fired
Make your endpoint idempotent by using notification.id as a dedup key — same notification.id = same event, even if delivered twice.
Verifying webhook authenticity
We sign every payload with HMAC-SHA256 using the webhook secret displayed when you save the webhook config. The signature is in the X-BrandBanta-Signature header.
To verify in your endpoint (Node.js example):
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyBrandBantaWebhook(
rawBody: string,
signatureHeader: string,
secret: string,
): boolean {
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
const expectedBuf = Buffer.from(expected, "hex");
const receivedBuf = Buffer.from(signatureHeader, "hex");
if (expectedBuf.length !== receivedBuf.length) return false;
return timingSafeEqual(expectedBuf, receivedBuf);
}Always verify on production endpoints to prevent forged payloads.
Rate limits
We don't impose webhook-side rate limits. The Slack-side limit is ~1 message/second per webhook URL; we batch and queue if you hit it.
For high-frequency notifications (e.g. scan-completed on a bulk of 25 scans), the queue ensures Slack doesn't 429 your channel.
See also
- Alerts — the rules that fire most webhook deliveries
- Webhook schema reference — full per-kind payload catalogue
- Insights & narratives — weekly-digest webhooks contain narrative data