# Slack integration

> Approve and reject query requests from Slack with interactive buttons, verified end-to-end with HMAC-SHA256 signatures and single-use action tokens.

- Documentation: Guides
- Updated: 2026-10-13
- Source: https://queryproxy.com/docs/slack-integration/
- Language: en-US
- Author: Muhammet Şafak

---
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.

## Setup

1. **Create a Slack app** for your workspace at
   [api.slack.com/apps](https://api.slack.com/apps).
2. Enable **Incoming Webhooks** and add a webhook for your approvals channel
   (e.g. `#db-approvals`).
3. Enable **Interactivity** and set the request URL to:

   ```
   https://your-queryproxy-host/webhooks/slack/interactions
   ```

4. 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` and `*.logic.azure.com` by default; the list is shared
   across chat providers; extend it with `QUERYPROXY_CHAT_WEBHOOK_ALLOWED_HOSTS`).
5. 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; turn it off with
`QUERYPROXY_CHAT_INCLUDE_SQL=false`) 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
&lt;name&gt;". For a written justification, reject from the web UI.

> **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-Signature` header must match
  `v0=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. The webhook
endpoint is also rate-limited.
