Approvals in Slack and Mattermost
When a run stops at an approval, Hyperspool posts it to a Slack or
Mattermost channel with Approve and Reject buttons. A click answers as
the person who clicked, with their roles. The message then updates to
show who answered and when, whether the answer came from chat, the
console, spool approval approve, a timeout or a cancel.
release is waiting for approval at shipShip 1.4 to prod?Expires at 2026-10-11 09:00 UTC[ Approve ] [ Reject ] [ Open in Hyperspool ]and once someone clicks:
release is waiting for approval at shipShip 1.4 to prod?Approved by Ann Lee at 2026-10-02 14:13 UTC[ Open in Hyperspool ]Posting a project’s approvals to a channel
Section titled “Posting a project’s approvals to a channel”A chat target says which channel a project’s approvals go to. The bot’s token is a credential in the project, and Slack’s signing secret is read from stdin or a prompt:
printf %s "$SLACK_BOT_TOKEN" | spool credential set slack-bot --kind bearerprintf %s "$SLACK_SIGNING_SECRET" | spool chat set releases --slack \ --channel C0123ABCD --credential slack-bot --signing-secretFor Mattermost, name the server:
printf %s "$MM_BOT_TOKEN" | spool credential set mm-bot --kind bearerspool chat set releases --mattermost --server-url https://chat.example.com \ --channel 4xk9s1dbp7n8z --credential mm-botEvery approval in the project is posted to every target. To post only
some, give a --when predicate. It reads the run.awaiting event
the same way as a handler’s --when does (see
Telling someone a run is waiting):
spool chat set releases --slack --channel C0123ABCD --credential slack-bot \ --when 'event.payload.workflow_name == "release"'Setting a Slack target again keeps its signing secret unless you pass
--signing-secret. spool chat list, get and delete manage targets;
in the console they are under Settings > Connections > Chat.
Changing targets needs admin on the project.
A gate is posted once per target, however long it waits. A run that was
already waiting when you added a target is posted the next time
Hyperspool picks the run up. Approvals inside a
child workflow are not posted, just as they don’t announce
run.awaiting.
Setting up the Slack app
Section titled “Setting up the Slack app”- Create an app at api.slack.com/apps, from scratch, in your workspace.
- Under OAuth & Permissions, add the bot scopes
chat:writeandchannels:history(groups:historyfor a private channel), andusers:readplususers:read.emailif you’ll use--match-email. The history scope lets a post that timed out be found in the channel on retry instead of being posted twice. Install the app and copy the Bot User OAuth Token (xoxb-…) into the credential. - Under Basic Information, copy the Signing Secret into
spool chat set … --signing-secret. - Under Interactivity & Shortcuts, turn interactivity on and set the
Request URL to
https://api.hyperspool.dev/api/v1/chat/slack/actions.spool chat setprints it, and the console shows it on the target. - Invite the bot to the channel (
/invite @your-app), and use the channel’s id, from the channel’s details.
A Slack target needs no --server-url unless your workspace is off
slack.com, and then it has to be a Slack host.
One app can serve every project: each target names its own credential and secret, and the request URL is the same for all of them.
Setting up Mattermost
Section titled “Setting up Mattermost”- Create a bot account (Integrations > Bot Accounts) and a token for it; put the token in the credential. Add the bot to the channel’s team and to the channel.
- Allow Mattermost to reach Hyperspool: the buttons call
https://api.hyperspool.dev/api/v1/chat/mattermost/actions. If Mattermost restricts outgoing connections, add that host under Developer > Allow untrusted internal connections. - Use the channel’s id, from View Info on the channel.
Hyperspool calls your Mattermost server at the --server-url you give,
so the server has to be reachable on a public address.
Mattermost doesn’t sign the requests its buttons send. What vouches for a click is a token Mattermost keeps on its server and never shows its users, so Hyperspool trusts the Mattermost server itself: its admins can answer as anyone linked on it.
Answering, and linking your chat account
Section titled “Answering, and linking your chat account”The first time you click, Hyperspool doesn’t know which of its users you are. The click answers nothing, and a reply only you can see gives you a code. Signed in to Hyperspool, enter it under Settings > Chat accounts, check the chat account it names, and link it. Or run:
spool chat link hscl_4f2a9c…Then click again. The code works once, for 15 minutes, and never comes as a link to click. Never enter a code someone else gave you: it would link their chat account to you, and whoever holds a linked chat account answers approvals as you.
The link lasts until you remove it under Settings > Chat accounts or
with spool chat unlink. A Slack link belongs to the Slack app you
clicked through: if the app’s signing secret is rotated, or approvals
come through another app, link again the same way.
With --match-email on a target, a chat user whose email (as Slack or
Mattermost reports it, and only once they have confirmed it there)
matches a Hyperspool user’s is known without linking. That trusts the
chat workspace’s admins with who your members are, so only an org admin
can turn it on. For Slack the app needs the users:read.email scope.
A click answers only if you could answer from the console: you need the
member role or above on the project. Otherwise the gate stays open and
the reply says why.
- An approval with no fields, or whose fields all have defaults, gets one Approve. It sends what the console’s one-click Approve sends, and the message says what that is.
- An approval whose one choice is a
selectof up to five options gets an Approve button per option. - Anything someone has to type goes to the console: the message offers Reject and a link to the run, where the form is.
Reject cancels the run, as it does in the console. Slack asks you to confirm it first.
When a message doesn’t appear
Section titled “When a message doesn’t appear”spool chat messages releases lists a target’s recent messages: posted,
answered, or retrying and why (channel_not_found when the bot isn’t in
the channel, for instance). The console shows the same under the target.
A post that keeps failing is retried with growing delays for a few
hours, then given up.
A run from outside the project
Section titled “A run from outside the project”A run started by a fork’s pull request is untrusted, and its message says so: its workflow name and prompt came from outside the project. Approving it from chat is the same as approving it in the console.