Skip to content

SILVIA Core Slack Bridge Reference

Version: 3.1

Namespace: CognitiveCode.Silvia.Extensions, Silvia.Extensions

Compile Flag: ENTERPRISE


Enterprise Slack Integration with Zero External Dependencies

SILVIA Slack Bridge connects SILVIA Core directly to Slack workspaces, enabling conversational AI deployment across enterprise communication channels. Users @mention SILVIA in any channel and receive threaded replies — processed through the same deterministic inference engine, LIVE CLI, and addressing protocol available in all other SILVIA integrations.

The bridge is implemented as two static classes in a single file with zero external dependencies. All HTTP operations use System.Net.Http and System.Net.HttpListener. Webhook payloads are verified with HMAC-SHA256 signature validation. A thread-safe queue architecture bridges Slack's asynchronous webhook delivery with SILVIA's single-threaded core, ensuring deterministic processing order without race conditions.

Why This Matters

Traditional chatbot frameworks require heavyweight SDKs, dependency trees of 15+ packages, and adapter layers that introduce latency and attack surface. SILVIA Slack Bridge uses exactly three Slack API operations — auth.test (once at startup), chat.postMessage (per response), and Events API webhook reception — in ~1,200 lines of fully auditable code with zero third-party risk.

MetricTypical Slack SDKSILVIA Slack Bridge
External dependencies15+ packages (~2.5 MB)0
Memory baseline~10 MB~20 KB
Memory per message~50 KB~4 KB
Processing overhead5–10 ms< 1 ms
CVE exposure surface15+ packages0 external packages

Architecture

The Slack integration comprises two static classes working in tandem:

ClassRole
SilviaSlackBridgeLow-level Slack HTTP API: token management, HMAC-SHA256 signature verification, chat.postMessage, JSON helpers
SilviaSlackServerWebhook HTTP listener, inbound event pipeline, thread-safe message queue, outbound response posting with chunking

Request Flow

Slack workspace                    Your server
───────────────────────────────────────────────────────────────────
  User: "@SILVIA help"


  Slack Events API ──POST──▶  SilviaSlackServer (HttpListener :8585)

                               ├─ 200 OK (immediate ACK)
                               ├─ event_id dedup
                               ├─ HMAC-SHA256 verify
                               ├─ bot message filter
                               ├─ app_mention filter
                               ├─ Addressing pipeline
                               ├─ Enqueue(SlackQueueItem)

                     ┌─────────┘

              Thread-safe ring buffer (32 slots)


              Host app main loop (FixedUpdate / tick)

                     ├─ Dequeue() → SlackQueueItem
                     ├─ core.GetResponseManaged(text)
                     ├─ apiApp.GetTextOutput() (drain stack)
                     └─ SilviaSlackServer.PostResponse()

                               ├─ Code-block wrapping
                               ├─ Chunk splitting (>3800 chars)
                               └──▶ SilviaSlackBridge.PostMessageToSlack()


                                    Slack chat.postMessage API


                                    User sees threaded reply

SILVIA is single-threaded. The webhook listener runs on a background thread and the ThreadPool. The queue is the bridge between the two — writes happen on the webhook thread, reads happen on the main thread.


Setup

1. Create the Slack App

Go to https://api.slack.com/apps and create a new app (from scratch).

OAuth & Permissions — Bot Token Scopes:

ScopePurpose
app_mentions:readReceive events when users @SILVIA
chat:writePost responses back to channels

Event Subscriptions:

  • Enable Events: On
  • Request URL: your public endpoint (see Exposing the Webhook)
  • Subscribe to bot events: app_mention only

Do NOT subscribe to message.channels or message.im. These fire in addition to app_mention and cause duplicate processing. The app_mention event alone covers all @SILVIA mentions in channels the bot is invited to.

Install App to Workspace, then collect:

  • Bot User OAuth Token (starts with xoxb-) — from OAuth & Permissions
  • Signing Secret — from Basic Information

2. Set Environment Variables

Set these at the machine level so they persist across reboots and are available to services.

Windows PowerShell (elevated):

powershell
[System.Environment]::SetEnvironmentVariable("SLACK_BOT_TOKEN", "xoxb-your-token", "Machine")
[System.Environment]::SetEnvironmentVariable("SLACK_SIGNING_SECRET", "your-secret", "Machine")
[System.Environment]::SetEnvironmentVariable("SLACK_WEBHOOK_PORT", "8585", "Machine")
[System.Environment]::SetEnvironmentVariable("SLACK_ADMIN_CHANNEL", "C0A15177WJ0", "Machine")

Linux/Mac:

bash
# Add to /etc/environment or your service's environment file
SLACK_BOT_TOKEN=xoxb-your-token
SLACK_SIGNING_SECRET=your-secret
SLACK_WEBHOOK_PORT=8585
SLACK_ADMIN_CHANNEL=C0A15177WJ0
VariableRequiredDescription
SLACK_BOT_TOKENYesBot OAuth token (xoxb-...)
SLACK_SIGNING_SECRETYesSigning secret for HMAC verification
SLACK_WEBHOOK_PORTNoListener port (default: 8585)
SLACK_ADMIN_CHANNELNoChannel ID for startup/shutdown notices

The admin channel is optional. If set, SILVIA posts "online" and "shutting down" messages there.

3. Initialize in Your Application

csharp
using CognitiveCode.Silvia.Extensions;
using Silvia.Extensions;

string botToken = Environment.GetEnvironmentVariable("SLACK_BOT_TOKEN",
    EnvironmentVariableTarget.Machine);
string signingSecret = Environment.GetEnvironmentVariable("SLACK_SIGNING_SECRET",
    EnvironmentVariableTarget.Machine);
string portStr = Environment.GetEnvironmentVariable("SLACK_WEBHOOK_PORT",
    EnvironmentVariableTarget.Machine);
string adminChannel = Environment.GetEnvironmentVariable("SLACK_ADMIN_CHANNEL",
    EnvironmentVariableTarget.Machine);

int port = 8585;
if (!string.IsNullOrEmpty(portStr)) {
    int.TryParse(portStr, out port);
}

if (!string.IsNullOrEmpty(botToken) && !string.IsNullOrEmpty(signingSecret)) {
    SilviaSlackBridge.Initialize(botToken, signingSecret);
    SilviaSlackServer.Start(port, botToken, adminChannel);
}

On startup, SilviaSlackServer.Start() calls Slack's auth.test API to resolve the bot's own user ID. This is required so the addressing pipeline can distinguish the bot's <@UID> from other users' mentions in the same message.

4. Expose the Webhook

Slack sends Events API payloads to a public HTTPS URL. You need to route that to your local HttpListener.

Development — ngrok:

bash
ngrok http 8585 --host-header=localhost:8585

The --host-header rewrite is required. Without it, ngrok forwards the public hostname in the Host header and Windows HttpListener returns 400 Bad Request because it doesn't match the registered prefix.

Copy the ngrok HTTPS URL into the Slack app's Event Subscriptions Request URL field:

https://your-subdomain.ngrok-free.app/slack/

The trailing /slack/ is required — that's the path HttpListener is bound to.

Production — URL ACL (no ngrok needed):

If running behind a reverse proxy or on a public-facing server, register the wildcard prefix so HttpListener accepts requests with any Host header:

powershell
netsh http add urlacl url=http://+:8585/slack/ user=Everyone

This only needs to be run once (persists across reboots).

5. Process Messages on the Main Loop

In your application's main tick/update loop, dequeue one message per tick:

csharp
SlackQueueItem slackItem = SilviaSlackServer.Dequeue();
if (slackItem != null) {
    Console.WriteLine("[Slack:" + slackItem.UserId + "] " + slackItem.Text);

    bool ok = core.GetResponseManaged(slackItem.Text);

    string output = core.ApiApp().GetTextOutput();
    bool first = true;
    while (output != null) {
        string prefix = "";
        if (first && !string.IsNullOrEmpty(slackItem.RespondingTo)) {
            prefix = slackItem.RespondingTo + " ";
        }
        first = false;

        bool isDiag = output.StartsWith("[") || output.StartsWith("Silvia:");
        if (!isDiag) {
            output = core.ApiApp().GetTextOutput();
            continue;
        }

        SilviaSlackServer.PostResponse(slackItem.ChannelId, prefix + output,
            slackItem.ThreadTs);
        output = core.ApiApp().GetTextOutput();
    }
}

6. Shutdown

csharp
SilviaSlackServer.Stop();
SilviaSlackBridge.Shutdown();

7. Invite the Bot

In each Slack channel where you want SILVIA to respond:

/invite @SILVIA

Addressing Protocol

SILVIA's native addressing protocol using @! (recipient) and ?@! (return address) works transparently through Slack. The server translates between Slack's <@UID> markup and SILVIA's internal protocol.

Inbound: Slack to SILVIA

The addressing pipeline runs in order before the message is queued:

StepMethodPurpose
1ParseReturnAddressExtract ?@! prefix if present
2CollectNonBotMentionsIf no ?@!, collect all <@UID> that aren't the bot
3StripAllMentionsRemove all <@UID> markup from the text
4StripSlackAutoLinksConvert <mailto:x|x> to x, <http://url|text> to url
5CLI blockReject text starting with $>

Outbound: SILVIA to Slack

When the host dequeues a SlackQueueItem, its RespondingTo field contains the addressing result. The host prepends it to the first output message before calling PostResponse:

  • ?@!Jason in the original message → first reply starts with Jason
  • ?@!@Brendan → first reply starts with @Brendan
  • ?@!<@U07ABC> → first reply starts with <@U07ABC> (native Slack ping)
  • @Jason @Brendan @SILVIA help (no ?@!) → first reply starts with <@U_JASON> <@U_BRENDAN> (native Slack pings to both)

Examples

User sendsSlackQueueItem.TextSlackQueueItem.RespondingTo
@SILVIA helphelpnull
?@!Jason @SILVIA helphelpJason
?@!@Brendan @SILVIA helphelp@Brendan
@SILVIA @Korey helphelp<@U0KOREY>
@Jason @Brendan @SILVIA reportreport<@U_JASON> <@U_BRENDAN>
@SILVIA test digest user@co.comtest digest user@co.comnull

See Also: SILVIA LIVE CLI for full addressing protocol documentation.


Inbound Event Pipeline

Every POST to /slack/ goes through this pipeline:

1.  GET? → health check JSON, return
2.  Not POST? → 405, return
3.  Read body
4.  url_verification? → return challenge, return
5.  *** 200 OK (immediate ACK — must beat Slack's 3-second retry window) ***
6.  Log: event_id, retry header, callback type
7.  Dedup by event_id (ring buffer, 64 slots) → drop if seen
8.  Drop if X-Slack-Retry-Num header present
9.  Missing signature headers? → drop
10. VerifySlackSignature() → drop if invalid
11. Contains "bot_id" or "subtype":"bot_message"? → drop (prevent loops)
12. Extract event section JSON → drop if missing
13. Event type != "app_mention"? → drop
14. Extract: user, text, channel, thread_ts, ts
15. Addressing pipeline (see above)
16. Empty after stripping? → drop
17. Starts with "$>"? → block, post rejection, return
18. Enqueue → if full (32), post overflow message, return

The 200 ACK at step 5 is critical. Slack retries after 3 seconds if it doesn't receive a 200. All processing after the ACK runs without time pressure.


Outbound Response Pipeline

SilviaSlackServer.PostResponse() handles formatting for Slack:

  1. Code block detection: If the output contains box-drawing characters (═, │, ─, ┌, └, ╔, ╚, ║, ╠) or exceeds 200 characters, it's wrapped in triple-backtick code blocks. This preserves formatted table and report layouts from SILVIA's console output.

  2. Chunking: If a code-blocked message exceeds 3,800 characters, it's split on newline boundaries into multiple messages, each wrapped in its own code block. This avoids a Slack rendering bug where very large code blocks cause the API to duplicate the message.

  3. Posting: Each chunk is sent via SilviaSlackBridge.PostMessageToSlack() with the thread timestamp, so all chunks appear in the same thread.


Security

Webhook Signature Verification

Every inbound POST is verified with HMAC-SHA256 using the Signing Secret:

  • Concatenate v0: + timestamp + : + raw body
  • Compute HMAC-SHA256 with the signing secret as key
  • Compare to the X-Slack-Signature header using constant-time comparison (prevents timing attacks)
  • Reject if the timestamp is more than 5 minutes old (prevents replay attacks)

Event Deduplication

Slack may deliver the same event multiple times (retries, infrastructure). The server maintains a ring buffer of the last 64 event_id values. Any event whose ID was already seen is silently dropped.

Bot Loop Prevention

Messages containing "bot_id" or "subtype":"bot_message" are dropped before processing. This prevents infinite loops where SILVIA responds to her own messages.

CLI Command Blocking

Messages that start with $> after stripping are blocked. The CLI command prefix is not exposed through Slack. A rejection message is posted back to the user.

Credential Management

All credentials are read from environment variables at startup and held only in memory. They are never logged, serialized, or persisted. On shutdown, SilviaSlackBridge.Shutdown() clears the token and secret and disposes the HttpClient.


API Reference: SilviaSlackBridge

Low-level Slack HTTP operations. Namespace: CognitiveCode.Silvia.Extensions.

MethodDescription
Initialize(botToken, signingSecret)Set up HttpClient with Bearer auth. Call once at startup. Returns bool.
Shutdown()Dispose HttpClient, clear credentials.
IsInitializedProperty: true if Initialize succeeded.
VerifySlackSignature(timestamp, signature, body)HMAC-SHA256 verification of a Slack webhook POST. Returns bool.
PostMessageToSlack(channelId, text, threadTs)Post a message via chat.postMessage. Thread-safe (locked). Returns bool.
PostDirectMessage(channelId, text)Convenience wrapper for PostMessageToSlack without threading.
ParseSlackEvent(json, ...)Extract event fields from raw JSON. Convenience for custom handlers.
ExtractJsonValue(json, key)Pull a string or primitive value from JSON by key prefix.
EscapeJsonString(input)Escape a string for embedding in a JSON value.

API Reference: SilviaSlackServer

Webhook listener, queue, and response posting. Namespace: Silvia.Extensions.

Methods

MethodDescription
Start(port, botToken, adminChannelId)Start HttpListener, resolve bot user ID via auth.test, begin accepting webhooks. Returns bool.
Stop()Stop listener, send shutdown notice if admin channel is configured.
Dequeue()Pop one SlackQueueItem from the queue. Returns null if empty. Call from main thread.
PostResponse(channelId, text, threadTs)Format and post a SILVIA output to Slack. Handles code blocks and chunking.
GetStatusReport()Returns a formatted string with server state, queue depth, counters, and uptime.

Properties

PropertyTypeDescription
IsRunningbooltrue if the listener is active.
PortintThe port the listener is bound to.
QueueDepthintCurrent number of queued messages.
TotalReceivedintLifetime count of messages enqueued.
TotalDroppedintLifetime count of messages dropped (queue full).
TotalProcessedintLifetime count of messages dequeued.
BotUserIdstringThe bot's own Slack user ID (resolved at startup).
StartedAtDateTimeUTC timestamp of server start.
LastMessageAtDateTimeUTC timestamp of last message received.

API Reference: SlackQueueItem

Represents a single inbound Slack message after pipeline processing.

FieldTypeDescription
UserIdstringSlack user ID of the sender
UsernamestringDisplay name (falls back to user ID)
TextstringCleaned message text (all markup stripped)
ChannelIdstringSlack channel to reply in
ThreadTsstringThread timestamp for reply threading
RespondingTostringAddressing result: plain name, @Name, or <@UID> (may be null)
ReceivedAtDateTimeUTC timestamp when the message was received

Health Check

GET /slack/ returns:

json
{"ok":true,"queue":0,"bridge":true}
  • queue: current queue depth
  • bridge: whether SilviaSlackBridge.Initialize() succeeded

Use this endpoint for load balancer health probes and monitoring.


Best Practices

1. Subscribe Only to app_mention

Do not subscribe to message.channels or message.im in your Slack app event subscriptions. These fire in addition to app_mention for the same @SILVIA message, causing duplicate processing. The app_mention event alone covers all mentions in channels the bot is invited to.

2. Use Machine-Level Environment Variables

Session-level environment variables don't survive restarts and aren't available to services. Always set credentials at the machine level so they persist across reboots.

3. Set Up the Admin Channel

The optional admin channel provides operational visibility. When configured, SILVIA posts "online" and "shutting down" messages, making it easy to monitor deployment status across your team.

4. Monitor Queue Depth

The QueueDepth property and health check endpoint expose current queue state. If the queue consistently approaches 32, your main loop isn't dequeuing fast enough — check for blocking operations in your tick handler.

5. Use the Status Report for Diagnostics

SilviaSlackServer.GetStatusReport() returns a comprehensive snapshot of server state, queue metrics, and uptime. Expose this through an admin command or monitoring dashboard.


Troubleshooting

400 Bad Request from ngrok

Cause: Windows HttpListener rejects requests where the Host header doesn't match the registered prefix. ngrok forwards the public hostname by default.

Fix: Use --host-header=localhost:8585 when starting ngrok:

bash
ngrok http 8585 --host-header=localhost:8585

Or register the wildcard prefix permanently:

powershell
netsh http add urlacl url=http://+:8585/slack/ user=Everyone

Duplicate Responses

Symptom: SILVIA posts the same reply 2–4 times.

Causes and fixes:

  1. Slack event retries: If the server doesn't ACK within 3 seconds, Slack retries. The server sends 200 OK immediately before any processing. If you're still seeing retries, check console logs for retry= values.

  2. Duplicate event subscriptions: If you subscribed to both app_mention and message.channels, Slack fires both events for the same message. Only subscribe to app_mention.

  3. Slack rendering duplication: Very large code blocks (>4,000 chars) can cause Slack to visually duplicate a single API post. The chunking logic splits messages at 3,800 characters to avoid this.

  4. Delayed events: If "Delayed Events" was enabled in the Slack app settings, Slack may queue events during downtime and deliver them all at once on reconnect. Disable this feature to avoid stale bursts.

Email Commands Fail with SMTP Syntax Error

Cause: Slack auto-links email addresses in message text, converting user@example.com to <mailto:user@example.com|user@example.com>. This markup passes through to SMTP drivers if not stripped.

Fix: The StripSlackAutoLinks step in the addressing pipeline converts these back to plain text automatically.

URL Verification Fails

When you first set the Request URL in the Slack app, Slack sends a one-time url_verification challenge. The server handles this automatically. If it fails:

  • Confirm the URL ends with /slack/
  • Confirm the server is running and reachable
  • Check console for [Slack] URL verification challenge log
  • Slack requires HTTPS — use ngrok or a TLS-terminating proxy

Bot Not Responding

  1. Is the bot invited to the channel? (/invite @SILVIA)
  2. Is the bot's app_mention event subscription active?
  3. Are environment variables set at the machine level?
  4. Check console for [Slack] Bot user ID resolved: — if this doesn't appear, auth.test failed and mention filtering will be degraded.
  5. Check console for [Slack] Queued messages — if nothing appears, the event isn't reaching the server.

Production Deployment Checklist

  • [ ] Machine-level environment variables configured
  • [ ] HTTPS endpoint with valid certificate (or ngrok for dev)
  • [ ] URL ACL registered if using http://+:port/ prefix
  • [ ] Slack app installed to workspace
  • [ ] Only app_mention event subscribed (no message.channels)
  • [ ] Bot invited to required channels (/invite @SILVIA)
  • [ ] Admin channel ID set for startup/shutdown notifications
  • [ ] Console logging monitored for [Slack] prefixed messages
  • [ ] Firewall allows inbound on the webhook port

See Also

  • ApiCoreGetResponseManaged() processes dequeued messages
  • ApiAppGetTextOutput() drains the response stack
  • SILVIA LIVE CLI — Addressing protocol, $> command syntax is disabled in SILVIA Slack Bridge
  • CI/CD & DevOps — Deployment patterns for SILVIA services

© Copyright Cognitive Code Corp. 2007-2026

SILVIA is a registered Trademark of Cognitive Code Corp.

SILVIA is a registered Trademark of Cognitive Code Corp.