Slack Connector
Continuously ingest Slack messages into CortexDB as conversation experiences.
The Slack connector tails messages from configured channels and writes them to CortexDB as conversation experiences. Threads and file shares are preserved.
Two ways to run this connector
Self-hosted — pip install 'cortexdb-connectors[slack]' then cortexdb-sync sync slack. Reads
your token from ~/.cortexdb/state.json (written by cortexdb init) or from env. Managed —
CortexDB runs the connector as a worker; configure it once from your dashboard. See the
Connectors overview.
1. Prepare credentials in Slack
api.slack.com/apps→ create a new app from scratch.- Add Bot Token Scopes:
channels:history— read public channelschannels:read— view channel infogroups:history— read private channels (optional)users:read— resolve author namesfiles:read— download shared files (required for file ingestion)
- Install the app to your workspace and copy the Bot User OAuth Token (
xoxb-...).
files:read is required for file shares
Without the files:read bot scope, Slack answers file downloads with a 200 HTML login page instead
of the file, so shared files are skipped. Add it if you rely on file ingestion.
2. Configure (managed dashboard)
Settings → Connectors → Add Connector → Slack. Paste the bot token, choose channels (default: all
public), set the scope template (e.g. org:<org>/source:slack/project:{channel}; channel is not an allowed scope type; placeholders
{channel}, {author}, {workspace}, {thread_ts}), pick Real-time (Events API webhook) or Polling
(default 10 s), and Start sync. Backfill preserves original message timestamps as observed_at for
bi-temporal correctness.
What gets written
Each Slack message becomes a POST /v1/experience:
{
"scope": "org:acme/source:slack/project:engineering",
"modality": "conversation",
"observed_actor": "user:alice@acme.com",
"content": {
"kind": "message",
"role": "user",
"text": "We should migrate the payments service to CockroachDB."
},
"context": {
"observed_at": "2026-03-15T10:30:00.123Z",
"labels": ["channel:engineering", "workspace:acme-corp", "thread=C123:1710502200.000100"],
"intent": "discussion"
},
"idempotency_key": "cxsrc:<56 hex>",
"source": { "schema_version": 2, "source_id": "connector:<digest>:<digest>", "...": "..." }
}The connector's internal record identity for this message is slack:C123:1710502200.000100:a1b2c3;
the key on the wire is the derived cxsrc: value (see the callout below).
| Slack event | Maps to |
|---|---|
| Channel message | experience with content.kind=message |
| Thread reply | Same, plus a thread=<channel>:<thread_ts> label (see below) |
| File share | experience with content.kind=blob_ref (file uploaded via POST /v1/blobs first; needs files:read) |
| Bot messages | Skipped by default; toggle "Include bots" to enable |
Record keys and threads
idempotency_keyisslack:{channel}:{ts}:{content-hash}for messages (the trailing content-version hash lets an edit land as a new version instead of colliding — there is no team-id segment). Files useslack:file:{channel}:{file_id}:{version}.- Thread replies attach a
thread=<channel>:<thread_ts>label, notcontext.preceded_by(the connector doesn't have the parent'sevt_…id at emit time). - Reactions are not ingested — emoji reactions never become content.
The key on the wire is derived
The key format above is this connector's internal record identity: it decides which source record an
event belongs to. The idempotency_key actually sent to CortexDB is a derived cxsrc:<56 hex> value
that also hashes the content, labels, observed time and actor, so an unchanged redelivery converges on
the same event while any edit lands as a new one, even where the key format itself is not
content-versioned. Every event also carries a source descriptor with the record's fingerprint. See
Source identity and keys.
Run it yourself
pip install 'cortexdb-connectors[slack]'
# One-time: get a CortexDB token + actor
pip install cortexdb-cli
cortexdb init
# Supply the connector's credentials
export SLACK_BOT_TOKEN=xoxb-... # required
# Sync once, or watch on a poll loop (point at your instance if self-hosted)
cortexdb-sync --api-url http://localhost:3141 sync slack
cortexdb-sync --api-url http://localhost:3141 watch slack --interval 60Cursor state persists in ~/.cortexdb/sync_state.json. For real-time ingestion, run
cortexdb-sync serve with SLACK_SIGNING_SECRET set, after
pip install 'cortexdb-connectors[slack,webhooks]' (serve needs the webhooks extra).
Optional settings
Set these as environment variables (or the matching keys in the cortexdb-sync YAML config). Lists are comma-separated. For on/off settings, 1, true, yes or on turns the setting on and any other value turns it off.
| Setting | What it does | Default |
|---|---|---|
SLACK_CHANNELS | Channel IDs to sync | Every channel the bot is a member of |
SLACK_INGEST_FILES | Ingest files shared in the synced channels (needs the files:read scope) | On |
SLACK_FILE_TYPES | File types to ingest when files are on | All types |
See also
- Connectors overview — the shared
cortexdb-syncmechanics - POST /v1/experience · Scopes · Lifecycle