Slack integration
Approve query requests from Slack with a Slack app or an incoming webhook — buttons, live message updates, /queryproxy, signed callbacks, single-use tokens.
Updated:
With the Slack integration, new query requests are announced in a channel with Approve / Reject buttons, and decisions flow back into QueryProxy — with the same rules as the web (role checks, no self-approval), cryptographically verified.
There are two ways to connect a team, and they can exist side by side:
| Slack app | Incoming webhook | |
|---|---|---|
| Set up by | An administrator once for the installation, then a DBA per team with Add to Slack | A DBA per team |
| Messages | Posted by the bot and updated in place through the whole life of the request | Posted once; updated after the decision |
| Extras | Thread summary, opt-in direct messages, the /queryproxy command, identity suggestions |
None |
| Secret | The app’s signing secret, in the environment | A signing secret per team, in the database |
When a team has both, the app is used. The webhook mode is unchanged from earlier versions and is described below.
The Slack app
One Slack app serves every team of an installation. The operator creates it once and sets its credentials in the environment; each team then installs it into its own workspace with one click.
Create the app
-
Create a Slack app at api.slack.com/apps.
-
Under OAuth & Permissions, add the redirect URL
https://your-queryproxy-host/integrations/slack/callbackand these bot scopes:chat:write,chat:write.public,commands,im:write,incoming-webhook,users:readandusers:read.email. -
Under Interactivity & Shortcuts, turn interactivity on with the request URL
https://your-queryproxy-host/webhooks/slack/interactions. -
Under Slash Commands, create
/queryproxywith the request URLhttps://your-queryproxy-host/webhooks/slack/commands. -
Under Event Subscriptions, set the request URL
https://your-queryproxy-host/webhooks/slack/eventsand subscribe to the bot eventsapp_uninstalledandtokens_revoked. -
Copy the client id, the client secret and the signing secret from Basic Information into the environment and restart the containers:
QUERYPROXY_SLACK_CLIENT_ID=... QUERYPROXY_SLACK_CLIENT_SECRET=... QUERYPROXY_SLACK_SIGNING_SECRET=...
Until these are set, the ChatOps page says which variables are missing and shows no Add to Slack button.
Install it for a team
A DBA of the team (or a system administrator) opens ChatOps and presses Add to Slack. Slack asks for consent and for the channel that should receive the messages; the browser then returns to QueryProxy. The page shows the workspace, the channel and who installed it. Pressing the button again installs into another channel or workspace.
The install uses OAuth v2 with a single-use state tied to the browser session
and valid for 10 minutes; a callback with a missing, stale or foreign state
is refused with 403 and Slack is not contacted. The bot token is stored
encrypted, per team. The webhook URL that Slack issues for the channel is not
stored. The audit log records slack.installed, never a token.
What the app does
- Cards that stay current. The request card is posted by the bot and edited in place as the request moves: approved, rejected, cancelled, running, completed or failed. There is no new message per step. A card replaced by a reminder is edited to say Moved: use the newer message, with its buttons removed.
- A thread summary. When a run finishes, the bot replies in the card’s thread with the status, the row count, the duration and a link. The reply has no SQL, no result data and no error text.
- Direct messages to approvers. An opt-in switch on the ChatOps page sends each team DBA who has a Slack member ID (except the requester) a short message with a link. It carries no SQL and no buttons.
The /queryproxy command
/queryproxy opens a form in Slack with the connections the person may use. The
submission files an ordinary request, marked as coming from Slack, that passes
the same guards and waits for the same approval. Before the form opens, and again
when it is submitted, QueryProxy checks that the Slack user is matched to an
active QueryProxy user, that the connection’s team has installed the app in that
workspace, and that the user may use the connection; otherwise only that person
sees an error. Sending the same form twice does not file two requests. If several
QueryProxy teams share one Slack workspace, the form lists the usable connections
of all of them, up to 100.
Matching Slack users to QueryProxy users
Slack clicks act as QueryProxy users through the Slack member ID on the user
(see the last step of the webhook setup). After an
install, and once a week, QueryProxy looks up each user who has none by e-mail
address and suggests a match. Suggestions appear under Slack matching
suggestions on the Users page. A system administrator approves one, which
links the member ID and is audited as chat_identity.linked with the source
slack_suggestion, or rejects it, which deletes the suggestion (the weekly
scan can propose it again). Nothing is linked automatically.
Removing the app
Remove app on the ChatOps page (DBA or administrator) deletes the stored bot
token and the workspace’s suggestions and writes slack.uninstalled with the
reason removed_by_user and the person. It does not tell Slack to revoke the
token, so also remove the app from the workspace in Slack. If the team has a
webhook configured, it keeps working.
QueryProxy also drops the install on its own when Slack sends app_uninstalled
or tokens_revoked, or rejects the bot token (slack.uninstalled, reason
token_rejected).
Settings
| Variable | Default | Meaning |
|---|---|---|
QUERYPROXY_SLACK_CLIENT_ID |
none | Client id of the Slack app |
QUERYPROXY_SLACK_CLIENT_SECRET |
none | Client secret of the Slack app |
QUERYPROXY_SLACK_SIGNING_SECRET |
none | Signing secret of the Slack app; verifies every request Slack sends |
QUERYPROXY_SLACK_RATE_LIMIT |
300 |
Requests per minute and Slack workspace (interactions, commands and events), counted after the signature is verified |
Before the signature is checked, each address may send 120 requests per minute to
the Slack endpoints; beyond that the answer is 429. The two limits are
separate so that forged traffic cannot use up a workspace’s budget. The Teams
endpoint keeps its own limit.
Upgrading runs two migrations that make a team’s webhook URL and signing secret optional, so a team can use the app alone. A team that has the app but no signing secret of its own cannot save a webhook URL until a system administrator enters one; no current secret is asked for in that first entry.
Incoming webhook: setup
-
Create a Slack app for your workspace at api.slack.com/apps.
-
Enable Incoming Webhooks and add a webhook for your approvals channel (e.g.
#db-approvals). -
Enable Interactivity and set the request URL to:
https://your-queryproxy-host/webhooks/slack/interactions -
In QueryProxy, open ChatOps. A DBA sets the webhook URL and the enabled flag; the app’s signing secret can only be entered by a system administrator. Both are stored encrypted. An integration cannot be saved without a secret, so the first save is an administrator’s. The webhook URL must be
https://and its host must be on the allowlist (hooks.slack.com,*.webhook.office.com,*.logic.azure.comand*.powerplatform.comby default; the list is shared across chat providers; extend it withQUERYPROXY_CHAT_WEBHOOK_ALLOWED_HOSTS). -
As a system administrator, open Teams & Users → Manage users and fill each reviewer’s Slack member ID (e.g.
U0123ABC— visible in a Slack profile under Copy member ID). This maps Slack clicks to QueryProxy users.
Breaking change — the signing secret is administrator-only. From this version a DBA can no longer enter or rotate it, and rotating an existing secret requires proving the current one. The reason is what an HMAC can and cannot say: it proves the caller knows the secret, never that a particular person approved. Anyone who could choose the secret could sign a callback approving their own request in a colleague’s name — and the audit trail would record the colleague. If a DBA configured ChatOps on an existing install, they already know the current value: rotate it once, as an administrator, after upgrading.
Removing an integration is administrator-only for the same reason: putting it back needs a signing secret, which only an administrator can set, so a delete would be a one-way door out of ChatOps for the whole team. The DBA’s day-to-day control is the Enabled flag — untick it and save to switch the integration off, with everything else kept.
What it looks like
When a request is submitted, QueryProxy posts a message with the requester,
the connection, a SQL preview (cut at 400 characters; string and number
literals show as ? by default, and QUERYPROXY_CHAT_INCLUDE_SQL switches the
preview to full or none) and three actions: Approve, Reject and
Open in QueryProxy. After a decision the message is updated in place —
buttons disappear and the outcome is shown, so a request can’t be decided twice
from a stale message.
Rejections from Slack record the generic reason “Rejected via Slack by <name>”. For a written justification, reject from the web UI.
An Approve press that would be an admin override (the approver is not a
DBA of the team, or decides their own request) is not applied. Every override
needs a written reason, so Slack answers with a link to the web form where you
enter it; the action token is not spent. A request that stays pending for
QUERYPROXY_PENDING_REMIND_AFTER hours (default 4) gets one reminder with a
fresh message, and a button of an earlier message that has expired answers with
a pointer to the newer one.
Breaking change — buttons in messages posted before this version no longer work. They carry no action token, so their callbacks are refused. Decide those requests in the web UI; every message posted from this version onwards behaves as described below.
Security
Every callback from Slack is verified before anything happens:
- Signature — the
X-Slack-Signatureheader must matchv0=HMAC_SHA256("v0:{timestamp}:{raw_body}", signing_secret), compared in constant time. - Replay protection — requests older than ±5 minutes (per
X-Slack-Request-Timestamp) are rejected. - Single-use action token — each Approve / Reject button carries a token bound to that one request, valid for 24 hours and spent on the first decision. So a callback can only answer a message QueryProxy actually posted, and only once: knowing the signing secret is no longer enough to forge a decision or replay a captured one. The token is burned only once the decision is actually recorded, so a click turned away by authorization does not use it up.
- Identity — the clicking Slack user must be mapped to a QueryProxy user who belongs to the integration’s team and holds the DBA role there — or is a system admin. Unmapped or unauthorized users get a private error message and nothing changes.
- Rules — self-approval is blocked in Slack exactly as on the web. As on the web, an admin may override the DBA-role rule and the self-approval block, and an admin’s approval that does so is flagged in the audit log.
A forged or stale callback returns 401 and changes no state. Callbacks are
verified with the Slack app’s signing secret (QUERYPROXY_SLACK_SIGNING_SECRET)
or, for the incoming-webhook mode, with the team’s own signing secret. The
/queryproxy command, the form it opens and the Events endpoint accept only
the app’s secret: a team-specific secret is refused there. The Slack endpoints are
rate-limited, as described under Settings.