Slack¶
Slack works with pg_relay through the webhook transport, posting to Slack's chat.postMessage Web API with a bot token. This is the transport's home ground: a JSON API pg_relay has no native knowledge of, driven entirely by pg_relay_notifier's configuration — the Processor needs zero Slack-specific code.
One-time Slack setup¶
Slack doesn't let software post with a personal login; you create a small Slack app in your workspace and Slack issues it a bot token (a credential starting xoxb-):
- At
api.slack.com/apps→ Create New App → From scratch → name it (e.g.pg-relay) in your workspace. - OAuth & Permissions → Bot Token Scopes → add
chat:write— "post messages" is the only permission pg_relay needs. - Install to Workspace → Allow, then copy the Bot User OAuth Token (
xoxb-...). Treat it like a password; the same page can regenerate it if it ever leaks. - In the channel notifications should land in, invite the app:
/invite @pg-relay. A bot that isn't a channel member getsnot_in_channelon every post. - Note the channel's ID (channel details → About tab,
C0XXXXXXX) — more robust than the channel's name in API calls.
Export the token on the Processor host (for a service, in its environment file — see Keeping Secrets Out of the Database):
Profile and message¶
// profile
{
"url": "https://slack.com/api/chat.postMessage",
"auth": {"style": "bearer_header", "secret": "_env:SLACK_BOT_TOKEN"},
"timeout_seconds": 30
}
// message — Slack's own request shape, sent verbatim
{
"channel": "C0XXXXXXX",
"text": "disk alert: volume is filling"
}
Because message is sent verbatim, anything chat.postMessage accepts works — text uses Slack's mrkdwn formatting, and richer layouts can supply a Block Kit blocks array alongside it. Email concepts (subject, cc/bcc, attachments) have no chat.postMessage equivalent and simply don't appear.
Interpreting the response¶
Slack has one famous quirk: it answers HTTP 200 for both success and failure, distinguishing in the JSON body — so the body, not the status code, decides. Have classify_webhook_response treat:
200with body"ok": trueassent— the body'stsis Slack's id for the posted message; return it as the provider reference.200with body"ok": falseasfailed— the body'serrorfield names the cause and makes ideal detail text:not_in_channel(invite the bot),channel_not_found(wrong channel ID),invalid_auth(bad or revoked token),msg_too_long.429, any5xx, andhttp_status = 0(no response) asretry— Slack rate-limits posting to roughly one message per second per channel, so429on a burst of notifications is routine and self-heals through the retry chain (the lower-casedretry-afterheader says how long Slack asked for).
Things Slack enforces¶
The chat:write scope only lets the bot post to channels it is a member of — the /invite in setup step 4 is functional, not cosmetic. And since a Slack "recipient" is a channel rather than an address, point separate notification profiles (or messages) at separate channel IDs to fan different event types into different channels.
Pinning a profile to one channel¶
When a profile should only ever post to one channel — an alerts profile that must never leak into a general channel, or vice versa — make it non-negotiable with body_merge instead of trusting every producer to pass the right id:
// profile
{
"url": "https://slack.com/api/chat.postMessage",
"auth": {"style": "bearer_header", "secret": "_env:SLACK_BOT_TOKEN"},
"body_merge": {"channel": "C0XXXXXXX"}
}
The Processor overlays the profile's channel onto the message immediately before sending, unconditionally replacing whatever the producer supplied — a wrong id, a stale copy-paste, or a deliberate redirect all land in the pinned channel anyway. Channel routing becomes a decision the profile owner makes once, not one every call site re-makes. Use an _env: reference as the value ("_env:SLACK_ALERT_CHANNEL") when the target channel differs between environments while the profile JSON stays identical.
Continue to Formatting Slack Messages — mrkdwn, Block Kit alert layouts, and deep links into complete guides.