Skip to main content
Slack is the richest approval channel VisIQ ships: an approval_required decision posts a message into a channel you choose, and a reviewer resolves it with Approve or Deny without leaving Slack. Setup takes about two minutes and produces three values you paste into VisIQ. The dashboard walks you through it at Connectors → Human-in-the-Loop → Slack; this page is the same flow written down, plus the one setting people most often miss.
Do not skip the Interactivity Request URL. It is the only thing that tells Slack where to send a button click. Without it, VisIQ’s message still posts and the buttons still render — but pressing one never reaches VisIQ, the approval sits unresolved, and the agent’s call times out and fails closed. The manifest below sets it for you; if you build the app by hand instead, set it yourself.

Before you start

  • A Slack workspace where you can create and install an app. Installing grants workspace-admin OAuth consent, so if you are not an admin you will need one to approve the install.
  • A VisIQ account with the connectors:manage permission — the save and delete endpoints are guarded by it.
  • A channel for approvals. A public channel is easiest; the app can post to a public channel without being invited.

Step 1 — Create the Slack app from VisIQ’s manifest

Go to api.slack.com/apps, choose Create New App, and pick From an app manifest. Select your workspace, then paste this:
Slack app manifest
The manifest pre-fills the permissions and the Interactivity Request URL in one step, which is why it is the recommended path.
Self-hosted or non-production VisIQ? Replace the request_url host with your own VisIQ origin, keeping the path exactly /api/integrations/slack/interactions. The dashboard’s setup panel renders the correct URL for the environment you are signed in to — copy it from there rather than typing it.
Create the app, then install it to your workspace and approve the consent screen.

What each permission is for

Every scope in the manifest is there for a specific delivery behaviour. None of them can read your channel history, your DMs, or your files. Two of these are worth calling out:
  • users:read is not optional if you want users:read.email. Slack documents that the two must be requested together, so a hand-built app that asks only for users:read.email is incomplete.
  • A missing im:write degrades silently. Owner direct messages need both users:read.email (to find the person) and im:write (to open the DM). Drop either one and approvals for a claimed agent quietly fall back to the shared channel and the owner’s email instead of failing loudly. VisIQ’s connection test reads the granted scopes back from Slack and warns you when this is the case — it is a warning, not an error, because channel-only installs are fully supported.

Step 2 — Collect the three values VisIQ needs

Open Connectors → Human-in-the-Loop → Slack in the VisIQ dashboard and fill in these three fields. They are the only inputs the connector takes. For a private channel, invite the bot first with /invite @VisIQ. Public channels are already covered by chat:write.public. Both secrets are envelope-encrypted at rest and are never returned by any VisIQ API — the connector list endpoint projects rows to a secret-free shape.

Step 3 — Run Test connection, then save

Test connection is a required step on first setup, not a nicety: the Save button stays disabled until a test passes. It does four things, and the third is the one that makes the buttons work.
  1. Calls Slack’s auth.test with your bot token to confirm the token is valid, and reports the workspace name back to you.
  2. Posts a real test message to your channel, so chat:write and channel reachability are proven by delivery rather than assumed.
  3. Reads your workspace’s team id from Slack and stores it with the connector. This is how inbound button clicks find your tenant — see below.
  4. Checks that your app was granted users:read.email and im:write, and warns (without blocking) if owner direct messages will be unavailable.
Two things it deliberately cannot check, and says so rather than claiming otherwise:
  • The signing secret is only format-checked. Slack signs requests to your Request URL, so there is no API call that validates a signing secret — it is proven live the first time someone clicks Approve or Deny.
  • Whether the Interactivity Request URL is set cannot be read back from Slack at all. Confirm it yourself in the app’s Interactivity & Shortcuts page. This is exactly why the manifest path is recommended.
Once the test passes, click Save. The card shows Connected.

Why the team id matters

An approval message posted by VisIQ carries buttons. When a reviewer clicks one, Slack posts the interaction to your Interactivity Request URL — a public endpoint that carries no VisIQ session. VisIQ therefore identifies the tenant by the workspace team id in Slack’s payload, looks up the matching connector, decrypts that tenant’s signing secret, and verifies the request signature before it will resolve anything. That chain only works if a team id is stored against your connector, and Test connection is what puts it there. A connector saved without a passing test has no team id, so every button click is answered with an opaque 401 and the approval is never recorded.

How VisIQ authenticates a button click

The interactions endpoint is public by design — it is a webhook, and Slack does not carry a user session. Authenticity comes from cryptography instead, in this order:
  1. The raw request bytes are read before any parsing, because Slack signs the exact bytes it sent.
  2. The workspace team id in the payload resolves the tenant’s connector.
  3. That connector’s signing secret verifies the X-Slack-Signature HMAC, and a request whose timestamp is more than 5 minutes off is rejected as a replay.
  4. Only after the signature verifies does VisIQ check that the signed button value names this connector’s own tenant.
An unknown workspace and an invalid signature return a byte-identical 401, so the endpoint cannot be used to discover which workspaces have a VisIQ connector.

The buttons a reviewer sees

Clicking a button on an item somebody already resolved is safe — Slack shows “This request was already resolved” rather than double-applying a decision.

Routing approvals to an agent’s owner

By default approvals go to the shared channel you configured. Set owner_email and a hitl_pathway of slack on an agent and its approvals are direct-messaged to that person instead, with the shared channel kept as the fail-safe if the DM cannot be delivered. Owner DMs are the feature that needs users:read.email and im:write; without them the approval still arrives, just in the channel.

The API behind the card

The dashboard is a client of the same endpoints you can drive yourself. All three require a session and the connectors:manage permission. The PUT body is validated strictly — an unrecognised key is rejected, not ignored:
PUT /api/connectors/hitl-channels/slack
secret may be omitted on a later update to keep the stored credentials, but the first save of a Slack connector is rejected with a 400 unless a secret is present. display_label is capped at 120 characters.

Troubleshooting

Slack is not reaching VisIQ, or VisIQ cannot identify your workspace. Check, in order: the app’s Interactivity & Shortcuts page has interactivity on with the Request URL pointing at your VisIQ origin’s /api/integrations/slack/interactions; and that you ran Test connection successfully, which is what stores the workspace team id the callback needs.
That is a warning for a private channel — run /invite @VisIQ in it. Public channels do not need the invite because chat:write.public covers them.
The Channel ID field wants the id (something like C0A1B2C3D4E), not the #channel-name. Copy it from the bottom of the channel’s details panel.
Your installed app is missing users:read.email, im:write, or both. Re-install or re-consent the app from the current manifest to pick them up. Approvals keep working in the meantime — they deliver to the channel and the owner’s email instead of a DM.
The approval window is capped at 120 seconds, and an unanswered request fails closed. That ceiling is a property of the harness, not of Slack — see Human-in-the-Loop for the pause-and-resume model and the mask fallback.

Human-in-the-Loop

How approvals pause a tool call, what the queue contains, and the timeout model.

Email approvals

The catch-all channel for agents that have no registered owner.