BDS0.1

Slack — Workspace Token Cache

Keeps each Slack workspace's bot token and user ready for other workflows, refreshed from Tooling every five minutes.

Reviewed 2026-11-06Markdown
Platform
n8n · Open in n8n
Status
Active
Workflow ID
aqaoUgxhGVLTnKUR
Nodes
18
n8n version
565b640c-345d-4880-b4c7-4622c45b3d95
Last saved in n8n
2026-11-06T19:41:23.849Z

Slack — Workspace Token Cache keeps the Slack bot of each workspace ready for the workflows that post to Slack. It reads the bot accounts from Tooling and Access, keeps one row per Slack workspace in an n8n Data Table for 15 minutes, and answers other workflows with the bot token and bot user of the workspace they ask for. It refreshes every workspace every five minutes, so a caller usually gets an answer without reaching Airtable.

It writes to no business system. Its only output is the n8n Data Table slack_workspace_token_cache, which is storage inside n8n, and the answer it returns to the workflow that called it. The Tooling and Access maintainer owns the bot accounts it reads.

Flow

Generated from the n8n version in the box above: every box is a node and every arrow is a connection in n8n. Pink pills are triggers, yellow hexagons are If and Switch nodes with their outputs on the arrows, blue cylinders read (Airtable, Data Table, HTTP GET), green cylinders write, purple boxes call another workflow, and dashed arrows are error outputs.

Errors

Refresh from Airtable

Answer from the cache

Triggers

error

error

error

error

error

error

error

true

false

true

false

true

false

error

Every 5 Minutes

Refresh Manually

Refresh All Workspaces

Token Request

Normalize Request

Refresh All?

Read Workspace Cache
get n8n Data Table slack_workspace_token_cache

Evaluate Cache

Refresh Needed?

Return Cached Result

Read Cache for Refresh
get n8n Data Table slack_workspace_token_cache

Read Bot Accounts
GET Tooling Tool Account

Build Cache Rows

Has Cache Rows?

Persist Workspace Tokens
upsert n8n Data Table slack_workspace_token_cache

Return Refreshed Result

Return Cache Error

Mermaid source
flowchart TD
  subgraph g0["Triggers"]
    n2(["Every 5 Minutes"]):::trigger
    n11(["Refresh Manually"]):::trigger
    n9["Refresh All Workspaces"]:::logic
    n16(["Token Request"]):::trigger
    n4["Normalize Request"]:::logic
    n10{{"Refresh All?"}}:::decision
  end
  subgraph g1["Answer from the cache"]
    n8[("Read Workspace Cache<br/><small>get n8n Data Table slack_workspace_token_cache</small>")]:::read
    n1["Evaluate Cache"]:::logic
    n12{{"Refresh Needed?"}}:::decision
    n14["Return Cached Result"]:::logic
  end
  subgraph g2["Refresh from Airtable"]
    n7[("Read Cache for Refresh<br/><small>get n8n Data Table slack_workspace_token_cache</small>")]:::read
    n6[("Read Bot Accounts<br/><small>GET Tooling Tool Account</small>")]:::read
    n0["Build Cache Rows"]:::logic
    n3{{"Has Cache Rows?"}}:::decision
    n5[("Persist Workspace Tokens<br/><small>upsert n8n Data Table slack_workspace_token_cache</small>")]:::write
    n15["Return Refreshed Result"]:::logic
  end
  subgraph g3["Errors"]
    n13["Return Cache Error"]:::logic
  end
  n2 --> n9
  n9 --> n4
  n11 --> n9
  n16 --> n4
  n4 --> n10
  n4 -.->|error| n13
  n8 --> n1
  n8 -.->|error| n13
  n1 --> n12
  n1 -.->|error| n13
  n7 --> n6
  n7 -.->|error| n13
  n6 --> n0
  n6 -.->|error| n13
  n0 --> n3
  n0 -.->|error| n13
  n5 --> n15
  n5 -.->|error| n13
  n10 -->|true| n7
  n10 -->|false| n8
  n12 -->|true| n7
  n12 -->|false| n14
  n3 -->|true| n5
  n3 -->|false| n15
  n15 -.->|error| n13
  classDef trigger fill:#fde7f3,stroke:#d6409f,color:#111
  classDef decision fill:#fff7e0,stroke:#c99a00,color:#111
  classDef read fill:#e8f1ff,stroke:#3b82f6,color:#111
  classDef write fill:#e7f8ee,stroke:#16a34a,color:#111
  classDef subflow fill:#f3e8ff,stroke:#9333ea,color:#111
  classDef wait fill:#f1f1f1,stroke:#888,color:#111
  classDef logic fill:#ffffff,stroke:#999,color:#111

Systems it touches

System Table Reads Writes
Tooling and Access Tool Account Read Bot Accounts Nothing
n8n (not a business system) Data Table slack_workspace_token_cache Read Workspace Cache, Read Cache for Refresh Persist Workspace Tokens

Read Bot Accounts is an HTTP Request node with the Airtable token credential “Airtable PAT (Fully Scoped)”. Its filter and the fields it reads use field IDs, so renaming Kind, Access Token, External Identifier or Workspace (External Identifier) in Tooling does not break it. See Tooling and Access automations for the Tool Account records.

Two workflows call it through Token Request and wait for the answer unless noted:

Calling workflow Calling node Inputs sent
Daily Digest Get Cached Notification Bot workspaceId; cacheOnly, forceRefresh and refreshAll false
Reading Reply Get Cached Bot workspaceId; cacheOnly true when the Slack request’s route is open, false otherwise
Reading Reply Refresh Workspace in Background workspaceId; forceRefresh true; does not wait for the answer

Triggers

Trigger When What it runs
Every 5 Minutes Every five minutes Refresh All Workspaces, then the refresh path for every workspace
Refresh Manually Someone runs it in the editor The same as Every 5 Minutes
Token Request Another workflow calls it The path chosen by its inputs

Normalize Request trims workspaceId and turns cacheOnly, forceRefresh and refreshAll into true only when the caller sent the boolean true. Without refreshAll, the workspace ID must look like a Slack team ID (T followed by capital letters and digits); otherwise it throws INVALID_WORKSPACE and the caller gets the error answer.

Refresh All? sends refreshAll to the refresh path and everything else to the cache path.

Answer from the cache

Read Workspace Cache gets the Data Table rows whose workspace_id is the requested workspace. Evaluate Cache accepts the row only when there is exactly one, it has an access_token, it has no cache_error, and expires_at is more than five seconds away.

Situation Answer
Valid row and no forceRefresh ok true, workspaceId, botSecret, botUserId, expiresAt, source cache. Return Cached Result hands it back
Two rows for the workspace Error CACHE_DUPLICATE
No row Error CACHE_MISS
Row with a cache_error That error code
Row about to expire, or forceRefresh Error CACHE_EXPIRED

On an error, Refresh Needed? goes to the refresh path, unless the caller sent cacheOnly: then Return Cached Result returns the error right away, without Airtable. The contract note on the canvas reserves cacheOnly for Slack interactive clicks, which must be answered quickly.

Refresh from Airtable

  1. Read Cache for Refresh reads every row of the Data Table, even when only one workspace is refreshed.
  2. Read Bot Accounts reads Tool Account rows whose Kind is bot, 100 per page, following every page, once per execution, with a 15-second timeout per request.
  3. Build Cache Rows groups the bot accounts by each value of Workspace (External Identifier) that looks like a Slack team ID. For each workspace to refresh it builds one row, or an error code:
Code When
AMBIGUOUS_BOT More than one bot account for the workspace
BOT_NOT_CONFIGURED No bot account for the workspace
BOT_TOKEN_MISSING Access Token does not start with the Slack bot token prefix
BOT_USER_MISSING External Identifier is not a Slack user ID (U followed by capital letters and digits)

The workspaces to refresh are the requested one, or, with refreshAll, every workspace found in Tool Account plus every workspace already in the Data Table. A workspace that is only in the Data Table gets BOT_NOT_CONFIGURED, which clears its token.

  1. Has Cache Rows? skips the write when there is nothing to write (only possible with refreshAll and no workspace anywhere).
  2. Persist Workspace Tokens upserts each row by workspace_id.
  3. Return Refreshed Result answers the caller. After refreshAll it returns counts: workspaces, ready and invalid. For one workspace it returns the same answer as a valid cache hit with source refresh, or the row’s error code, or CACHE_EXPIRED.

Fields written

Tool Account fields read (Tooling and Access)

Field Used for
Kind Only rows with bot are read
Workspace (External Identifier) The Slack workspace IDs the bot belongs to
Access Token The bot token copied to the cache
External Identifier The bot’s Slack user ID

Data Table columns written (Persist Workspace Tokens)

Column Value
workspace_id Slack workspace ID; the upsert key
access_token Bot token, or empty when there is an error code
bot_user_id Bot’s Slack user ID, or empty when there is an error code
source_record_id Record ID of the Tool Account row, or empty when none matched
cache_error Empty, or one of the codes under Refresh from Airtable
expires_at Write time plus 15 minutes, in milliseconds; 0 when there is an error code

Nodes

The workflow has 18 nodes: 17 that run and one sticky note. For an If node, output 0 is true and output 1 is false. Nodes set to continue on an error output (Normalize Request, Read Workspace Cache, Evaluate Cache, Read Cache for Refresh, Read Bot Accounts, Build Cache Rows, Persist Workspace Tokens and Return Refreshed Result) send failures out of output 1, drawn as dashed arrows in the flow.

Node Type What it does
Every 5 Minutes scheduleTrigger Schedule trigger, every five minutes
Refresh Manually manualTrigger Manual trigger in the editor
Refresh All Workspaces set Sets refreshAll = true for both refresh triggers
Token Request executeWorkflowTrigger Called by another workflow with workspaceId, cacheOnly, forceRefresh, refreshAll
Normalize Request code Validates the workspace ID and normalizes the flags
Refresh All? if refreshAll or a single workspace
Read Workspace Cache dataTable Data Table rows for the workspace
Evaluate Cache code Returns the cached bot when valid; otherwise says why
Refresh Needed? if Refresh now or answer as is
Return Cached Result noOp Passes the answer back to the caller unchanged
Read Cache for Refresh dataTable Every row of the Data Table
Read Bot Accounts httpRequest Tool Account rows with Kind bot, all pages
Build Cache Rows code One cache row per workspace, or an error code
Has Cache Rows? if Anything to write or not
Persist Workspace Tokens dataTable Upserts the rows by workspace_id
Return Refreshed Result code Answers the caller after a refresh
Return Cache Error code Answers TOKEN_CACHE_UNAVAILABLE with the stage
Cache Contract stickyNote Contract note on the canvas; does not run

Connections

Return Refreshed Result has no outgoing connection on output 0: its item is the answer returned to the caller.

From Output To
Every 5 Minutes 0 Refresh All Workspaces
Refresh Manually 0 Refresh All Workspaces
Refresh All Workspaces 0 Normalize Request
Token Request 0 Normalize Request
Normalize Request 0 Refresh All?
Normalize Request 1 Return Cache Error
Refresh All? 0 Read Cache for Refresh
Refresh All? 1 Read Workspace Cache
Read Workspace Cache 0 Evaluate Cache
Read Workspace Cache 1 Return Cache Error
Evaluate Cache 0 Refresh Needed?
Evaluate Cache 1 Return Cache Error
Refresh Needed? 0 Read Cache for Refresh
Refresh Needed? 1 Return Cached Result
Read Cache for Refresh 0 Read Bot Accounts
Read Cache for Refresh 1 Return Cache Error
Read Bot Accounts 0 Build Cache Rows
Read Bot Accounts 1 Return Cache Error
Build Cache Rows 0 Has Cache Rows?
Build Cache Rows 1 Return Cache Error
Has Cache Rows? 0 Persist Workspace Tokens
Has Cache Rows? 1 Return Refreshed Result
Persist Workspace Tokens 0 Return Refreshed Result
Persist Workspace Tokens 1 Return Cache Error
Return Refreshed Result 1 Return Cache Error

Settings and error handling

Setting Value
Execution order v1
Time zone America/Recife
Execution timeout 60 seconds
Save successful executions No
Save failed executions No
Save manual executions No
Save execution progress No
Error workflow None
Retry on fail Off on every node
Caller policy Workflows from the same owner
Available in MCP Yes

Every failure inside the flow goes to Return Cache Error, which answers ok false, error TOKEN_CACHE_UNAVAILABLE and stage: the name of the last node it finds executed, checked from the end of the refresh path backwards. Failures in the nodes with an error output become this answer instead of a failed execution; the caller decides what to do with it.

Known gaps

These come from the node configuration. Executions are not saved, so n8n keeps no history of past runs to compare with.

Gap Effect
The answer carries the bot token in botSecret Any workflow of the same owner that calls it receives the token in its own execution data
The Data Table stores the bot token in clear text in access_token Anyone who can open the Data Table in n8n can read every cached token
No execution is saved and errors become a normal answer A broken scheduled refresh leaves no trace in n8n; it shows up only as callers getting CACHE_EXPIRED or TOKEN_CACHE_UNAVAILABLE
stage in the error answer is the last executed node found, not the node that failed It points to the right area, not always to the exact node
A one-workspace refresh reads the whole Data Table and every bot account Each forced refresh costs as much as a full refresh in Airtable calls
Two refreshes can run at the same time (schedule and a caller) Both upsert the same rows; the last write wins, with the same data
The cache never asks Slack whether a token still works A token revoked in Slack keeps being served until someone changes Access Token in Tool Account

Recover a partial write

The Data Table can be rebuilt at any time, so recovery is a refresh.

What happened What to do
Callers get CACHE_EXPIRED or CACHE_MISS for a workspace that has a bot Run Refresh Manually; if the error stays, check the workspace’s Tool Account row
A workspace answers AMBIGUOUS_BOT Keep one Tool Account with Kind bot for that workspace, then refresh
A workspace answers BOT_TOKEN_MISSING or BOT_USER_MISSING Fix Access Token or External Identifier on its Tool Account, then refresh
A refresh stopped halfway Run Refresh Manually; the upsert replaces every row
Two rows for one workspace (CACHE_DUPLICATE) Delete one row in the Data Table, then refresh

Keep this page in sync

The box at the top shows the n8n version this page describes. Open the workflow in n8n: when its last save is newer than the date in the box, this page is out of date until the next review. Every node and every connection of that version is listed in Nodes and Connections.