Before You Begin
- Chert Notifications and Chert Agent are installed via the OAuth flow described in Install.
OAuth token storage
| Property | Value |
|---|---|
| Where stored | Per-project row in the slack_installations table |
| Token type | Slack bot token (xoxb-…) |
| Exposure to browsers | Never returned by the API |
| Status endpoint | Returns workspace metadata only — team id, team name, bot user id, install timestamp |
| Disconnect | POST /api/slack/disconnect deletes the row; the bot can no longer post for that project |
auth.revoke on disconnect. To fully
revoke the install on Slack’s side, remove the app from Slack’s
Manage apps page.
Signing-secret verification
Every inbound webhook from Slack is verified before any logic runs.| App | Signing secret env var |
|---|---|
| Chert Agent events webhook | SLACK_SIGNING_SECRET |
| Chert Notifications events webhook | SLACK_HELPER_SIGNING_SECRET |
| Field | Value |
|---|---|
| Basestring | v0:<x-slack-request-timestamp>:<raw body> |
| Header | x-slack-signature: v0=<hex hmac-sha256> |
| Freshness window | 5 minutes (timestamp must be within ±300 s of server time) |
| Comparison | Timing-safe |
401 with a structured error body —
{ ok: false, code: 2004, message, retryable: false, trace_id } — and
the event is dropped. If the Slack signing secret is missing
server-side the route returns 500 with code 3003
(PROVIDER_NOT_CONFIGURED, retryable: false) instead. Slack’s
one-time url_verification handshake is the only event accepted
without signing — it still returns a bare 200 with the raw
challenge string, unchanged.
Genuine error paths across /api/slack/* now carry the structured
body (numeric code, generic message, retryable, trace_id);
the OAuth callback redirects to /settings with a stable short
reason slug (no_code, slack_denied, oauth_exchange_failed,
not_configured, …) rather than raw error text in the URL. See the
Messaging API error scheme for the code registry.
Event deduplication
Slack retries webhooks on slow responses or network errors. Chert deduplicates by Slack’sevent_id for 10 minutes, so retried events
do not double-fire actions like proposals or sends.
Channel-binding scope
| Scope | What it means |
|---|---|
| One channel per project | Notifications and agent posts target the bound channel only. |
| Cross-project isolation | Each project’s bot token, settings, and canvases are independent of every other project, even on the same workspace. |
| Private channels | The bot must be invited (/invite @Chert Notifications) before it can post or write canvases. |
What the bot reads vs writes
| Operation | Type | Surface |
|---|---|---|
| List channels for the picker | Read | conversations.list |
| Post reply notification cards | Write | chat.postMessage |
| Post agent responses | Write | chat.postMessage |
| Update agent progress messages | Write | chat.update |
| Read thread history for context | Read | conversations.replies (only on threads under bot-posted messages) |
| Read reactions on proposal cards | Read | reactions.get (only on bot-posted messages) |
| Create and edit canvases | Write | conversations.canvases.create, canvases.edit, canvases.delete |
| Upload xlsx previews | Write | files.getUploadURLExternal, files.completeUploadExternal |
Webhook signing for outbound notifications
WhennotificationMethod is webhook or both and a webhookSecret
is configured, every outbound POST from Chert carries an
x-chert-signature: v1,<ts>,<hex> header. The signature is HMAC-SHA256
over <ts>.<raw-body> using your webhookSecret. Verify on receipt
to authenticate the payload.
Sub-processors
| Vendor | Purpose | Data |
|---|---|---|
| Slack | Notification surface, agent surface | Lead names, message bodies, conversation excerpts, project metadata |
| Anthropic | LLM drafting and classification | Same — used to draft replies and answer thread questions |
| Resend | Email notifications | Operator email addresses, reply previews |
See Also
Configuration
Routing, flags, and the Nth-reply filter.
Notifications
Card anatomy and thread-reply triage.

