Chat notifications
SemiLayer can post any of its system notifications — ingest completions,
quota warnings, billing changes, member invites, and more — directly
into a Slack channel or Discord channel. Opt-in only. Installing
the bot does nothing until somebody runs /semilayer notify on <type>
inside a specific channel.
This page covers what the surface looks like, how to wire it up, and the rules around mandatory types and channel cleanup.
This is the second product surface in the SemiLayer ChatOps
integration. The first is identity linking (/semilayer login),
which most teams set up once. After that, every notification
subscription is per-channel and admin-gated.
What you can subscribe to
16 notification types are supported in chat today. Their data shapes are identical to email + in-app, only the rendering differs.
| Type | When it fires | Mandatory? |
|---|---|---|
welcome | Member joins SemiLayer | yes |
member.invited | Admin invites a new member | yes |
member.joined | Invited member accepts | |
ingest.completed | Ingest run finishes successfully | |
ingest.failed | Ingest run dead-letters | |
quota.warning | Org crosses 80% of a metered quota | |
quota.grace | Quota exceeded but in grace period | |
quota.exceeded | Quota fully exceeded — hard stop | yes |
quota.reset | Cycle rollover restored quota | |
quota.restored | Admin lifted a per-org override | |
feedback.received | Customer submitted feedback | yes |
admin.daily_digest | Daily ops summary for platform admins | yes |
billing.payment_failed | Stripe charge failed | yes |
billing.plan_changed | Org switched plan tier | |
billing.subscription_canceled | Subscription canceled | yes |
billing.cycle_reset | New billing cycle started |
Mandatory types fire on critical account state. They cannot be turned
off individually — /semilayer notify off quota.exceeded is rejected
with a pointer at notify none (which clears every subscription on
the channel, mandatory or not).
Run /semilayer notify types from any channel to print the same list
inline.
Setup
The end-to-end story takes three steps.
1. Connect a workspace
In Console → Integrations, click Add Slack workspace or Add Discord workspace and complete the OAuth popup. The chosen workspace is bound to the current org. Only org admins can install or revoke.
2. Link your chat user
From any channel where the bot is installed, run:
You'll get a one-time link to your browser, sign in with the same
identity you use for Console, and the chat user is bound to your
SemiLayer member. This is per-user, per-workspace, and only needs to
happen once. Required for every command including notify.
3. Subscribe a channel
Invite the bot to the channel (Slack: /invite @semilayer; Discord:
add to the channel role), then run any of:
That's it — from this point the channel receives a Block Kit (Slack) or embed (Discord) every time a matching notification fires for the org bound to the workspace. The bot only posts in channels that have an explicit subscription. No auto-add, no shadow channels.
Permission rules
/semilayer notify has a three-layer permission gate, in order:
- Workspace must be installed. Bot in a channel with no workspace row returns "this workspace isn't connected — run the install flow in Console first."
- Chat user must be linked. Anyone unlinked gets the
"Run
/semilayer login" pointer. - State changes need admin role.
notify on/off/all/nonerequire the linked SemiLayer user to beowneroradminon the org.notify listandnotify typesare read-only and any linked member can run them.
DMs are explicitly rejected with "run this inside a channel, not in DMs" — there's no concept of a "DM subscription" in v1.
Multiple channels, multiple subscriptions
Two channels in the same workspace can subscribe to the same type —
both will receive the post. One channel can subscribe to many types.
There's no de-dup across channels: if #alerts and #ingest-ops both
subscribe to ingest.failed, both get the message.
Channel renames & deletes
- Renames are picked up by a weekly background sweep that asks the
provider for the canonical channel name. The freshest name is what
shows in
notify list. If you renamed the channel today, the next sweep will reflect it within 7 days; the subscription continues working in the meantime because it's keyed on the channel ID, not the name. - Deletes self-heal: the first post that comes back with
channel_not_found(Slack) or 10003/Unknown Channel (Discord) drops the subscription rows for that channel. The notification itself isn't lost — it still goes to email + in-app per your preferences.
Workspace revoke
Disconnecting a workspace from Console → Integrations → ⋯ → Disconnect
sets revokedAt on the workspace row. The fanout query joins on
revokedAt IS NULL, so revoking a workspace silences every channel
subscription under it instantly — no need to clear them one by one.
How it interacts with email & in-app preferences
Chat is a separate channel from email + in-app. Toggling
/semilayer notify on ingest.failed for #alerts does not affect
your personal email or in-app preferences for that type, and vice
versa. The fanout to chat is gated only by chat_subscriptions rows;
the per-user notification_preferences table is consulted only for
email + in-app.
Brand & rendering
Each type renders with a fixed severity color so the channel signal is scannable at a glance:
| Severity | Color hex | Used by |
|---|---|---|
| Error | #dc2626 | ingest.failed, quota.exceeded, billing.* |
| Warning | #f59e0b | quota.warning, quota.grace |
| Success | #10b981 | ingest.completed, quota.reset, quota.restored |
| Primary | #8B5CF6 | welcome, member.invited, admin.daily_digest |
| Secondary | #3B82F6 | member.joined, feedback.received |
Slack messages prefix the header with a severity emoji (Block Kit's
modern equivalent of the deprecated attachments color). Discord
embeds set the embed color field to the integer form of the same
hex. Every message ends with an "Open in Console" deep-link to the
relevant route (e.g. an ingest.failed post links to the failed
ingest run).
FAQ
Can two channels in different workspaces subscribe to the same type? Yes — each workspace binds to its own org, so the subscription is org-scoped.
Can I subscribe to a notification for a different org than the one bound to the workspace? No. v1 ties the workspace to one org; notifications fire org-scoped and only post into that org's workspaces.
What happens if the bot is in the channel but I run notify on
without linking first? You get the "Run /semilayer login"
pointer. The subscription is not created until you're linked +
admin.
Does notify on count against my API quota? Each post the bot
makes counts as one billable request against the org's metered API
quota — same as email and webhook deliveries.
Can I see who subscribed a channel? Today the audit trail lives
on chat_subscriptions.created_at. A first-class admin view of
"who subscribed what" lands with Phase 5.
Troubleshooting
/semilayer notify onreturns "Only org admins can manage…" — Your chat user is linked but maps to a non-admin SemiLayer member. Ask an org admin to either run the command or promote you in Console → Team.- Bot doesn't post in a private channel. Slack requires the bot
to be invited (
/invite @semilayer). After inviting, retry the notification trigger — the next post will land. notify listshows a stale channel name. Names refresh weekly. The subscription itself works; only the displayed name lags.- Posts stopped arriving after I renamed/archived the channel.
The next failed post drops the subscription. Re-run
notify on <type>from the new channel.