hermosodocs

Alerts: Slack, Discord and webhooks

Hermoso can tell you when something happens on a brand: a render finished, a scheduled post went out, a draft is waiting for you. Point those alerts at a Slack channel, a Discord channel, or any HTTPS endpoint you run (a Zapier, Make or n8n catch hook works too). Each brand has its own three alert channels, and setting them up costs nothing.

Connect a channel

Each alert channel is a URL you paste once.

ChannelWhat to paste
slackA Slack incoming-webhook URL. It starts with https://hooks.slack.com/.
discordA Discord channel webhook (channel settings, Integrations, Webhooks, New Webhook, Copy URL). It starts with https://discord.com/api/webhooks/.
webhookAny public https:// URL that should receive a JSON POST per event. Private and internal addresses are refused.

Before anything is saved, Hermoso sends the URL a hello message and reads the answer. A URL that Slack or Discord says is deleted or revoked is refused and nothing is stored. Any other problem is saved with a note, and the alert is retried.

Alerts are not posts. On Slack and Discord the webhookUrl field is for alerts. Posting your own content to a channel with post_to_slack or post_to_discord uses a separate URL, announceWebhookUrl, or the Hermoso app for that workspace. Pass one per call.

Choose the events

Events come in groups, and each channel chooses its own. A channel you have never configured receives every group except inbox, which you have to name.

GroupEventsOn by default
rendersrender.finished, render.failedYes
postspost.published, post.partial, post.failedYes
approvalsapproval.awaiting: autopilot drafts waiting for your reviewYes
watchwatch.finding: new competitor ads from the competitor watchYes
creditscredits.lowYes
leadslead.created: a new lead-form leadYes
connectionsaccount.connected, account.disconnectedYes
inboxcomment.received, message.received: new Facebook and Instagram comments and DMsNo, name it to receive it

Change a channel's groups with set_alert_events, for example {"provider": "slack", "events": ["renders", "posts"]}, or "all". An empty list keeps the channel connected and sends it nothing. An unknown group name is refused by name and nothing changes. The reply is read back from the saved setting.

The webhook payload

A webhook gets one JSON POST per event, with content-type: application/json and the user agent Hermoso-Alerts/1. The body always has event and at (when it was sent, ISO 8601), plus that event's own fields. The test event looks like this:

{"event": "test", "ok": true, "message": "Test alert from Hermoso.", "at": "2026-11-04T15:02:11.482Z"}

The fields on account.connected, comment.received and message.received are listed under Webhooks on the partners page.

Verify the signature

Deliveries are signed the Standard Webhooks way. The signing secret (it starts with whsec_) is shown once, when you first connect the webhook. Every delivery carries three headers:

import crypto from 'node:crypto';

function verify(req, rawBody, secret) {
  const id = req.headers['webhook-id'];
  const ts = req.headers['webhook-timestamp'];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // older than 5 minutes
  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const want = 'v1,' + crypto.createHmac('sha256', key).update(`${id}.${ts}.${rawBody}`).digest('base64');
  return String(req.headers['webhook-signature'] || '').split(' ').some((sig) =>
    sig.length === want.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(want)));
}

Sign the raw body exactly as received, before any JSON parsing. To replace the secret, call rotate_webhook_signing_secret: the new secret is returned once, and the old one keeps signing alongside it for 24 hours so your receiver has a day to switch.

Delivery and retries

Check and test

list_alert_settings shows each channel: connected or not, the groups it receives, its health (delivering, not delivering, or stopped and waiting for a new URL), whether the webhook is signed, and the recent deliveries with their HTTP status and any retry queued. send_test_alert sends a test to one channel or to all of them and reports what each answered. A test that gets through also clears a not-delivering or stopped state. All four tools are free.