QueryProxy
Documentation menu

Microsoft Teams integration

Announce query requests in a Teams channel and wire approvals through an HMAC-verified action endpoint with single-use action tokens.

Updated:

The Teams integration has two halves: announcements into a channel via a Workflows (or legacy connector) webhook, and an action endpoint your automation (an outgoing webhook or a Power Automate flow) can call to approve or reject.

Announcements

  1. Create a Workflows (Power Automate) webhook for your Teams channel and copy its URL. A URL on *.powerplatform.com or *.logic.azure.com receives an Adaptive Card. Legacy Office 365 connector URLs (*.webhook.office.com; an outlook.office.com URL only after you add that host to QUERYPROXY_CHAT_WEBHOOK_ALLOWED_HOSTS) still receive a MessageCard, but Microsoft has retired Office 365 Connectors: its retirement notice (April 14, 2026 update) says they no longer function after the rollout of May 18 to May 22, 2026. Use a Workflows URL.
  2. In QueryProxy, open ChatOps. A DBA pastes the webhook URL and sets the enabled flag; the HMAC secret (any strong random string — you’ll use the same secret for the action endpoint below) can only be set 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.com and *.powerplatform.com by default; the list is shared across chat providers; extend it with QUERYPROXY_CHAT_WEBHOOK_ALLOWED_HOSTS).

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. An HMAC proves that the caller knows the secret, not that a particular person approved: anyone who could choose the secret could sign a call approving their own request in a colleague’s name, and the audit trail would record the colleague. On an existing install, rotate the secret once as an administrator after upgrading.

Removing an integration is administrator-only for the same reason: putting it back needs a signing secret only an administrator can set. To switch the integration off, a DBA unticks the Enabled flag and saves.

New requests are posted as a card with the requester, connection, statement count, a SQL preview and a Review in QueryProxy link. The preview is cut at 400 characters and, by default, shows string and number literals as ?; QUERYPROXY_CHAT_INCLUDE_SQL switches it to full or none. Decisions are announced back to the channel as they happen.

On a Workflows URL the card is an Adaptive Card 1.5 with a fact list (requester, connection, statements), the SQL preview in a monospace text run (not parsed as markdown, so a*b, c*d arrives as written) and an Open URL action. The body is the Workflows “post card” message: type: message with the card in attachments[0].content.

Breaking for custom flows. A flow on a logic.azure.com URL that reads MessageCard fields (title, summary, sections[0].facts) now reads them empty. Read the Adaptive Card from attachments[0].content, or pass the body on unchanged. The queryproxy envelope is unchanged and stays at the top level.

Each card also carries a single-use action token for that request, in a queryproxy envelope at the top level of the payload:

{
  "queryproxy": { "request_id": 123, "action_token": "…" },
  "type": "message",
  "attachments": [{ "contentType": "application/vnd.microsoft.card.adaptive", "content": { "type": "AdaptiveCard", "version": "1.5" } }]
}

On a legacy connector URL the same envelope sits next to "type": "MessageCard".

Teams renderers ignore the unknown key, so the token stays out of the visible card; your automation reads it there and echoes it back to the action endpoint.

The action endpoint

To approve or reject from Teams-side automation, call:

POST https://your-queryproxy-host/webhooks/teams/actions
X-QueryProxy-Timestamp: <unix seconds>
Authorization: HMAC <base64(HMAC_SHA256("{timestamp}:{raw_body}", secret))>
Content-Type: application/json
{
  "action": "approve",
  "request_id": 123,
  "actor_id": "<AAD object id>",
  "token": "<action token>",
  "reason": "optional — used for reject"
}
  • action — approve or reject.
  • request_id — the request number from the announcement card.
  • actor_id — the Azure AD object id of the approver. QueryProxy resolves it to a user through the Teams ID mapping an admin sets in Teams & Users → Manage users (an unlinked id is rejected with 422); it is never resolved by a self-declared email. The resolved user must also belong to the integration’s team (a system admin always passes this check); otherwise the call is refused with 403 before it ever reaches the policy. The usual rules still apply: they need the DBA role in the request’s team and cannot decide on their own request. As on the web, an admin may override both rules, and an admin’s approval that does so is flagged in the audit log.
  • token — required: the single-use action token from the card’s queryproxy envelope. It is bound to that one request, expires after 24 hours and is burned on the first decision (only once that decision is actually recorded, so a call the policy turns away does not use it up), so a captured call cannot be replayed and the shared secret alone cannot conjure a decision. A call without a live token (missing, expired or already spent) is refused with 422 and a deliberately vague message.
  • reason — optional for reject; defaults to “Rejected via Teams by <name>”. For approve it is required whenever the approval is an admin override (the approver is not a DBA of the team, or decides their own request): without a reason (1 to 1000 characters) the call is refused with 422 and the token is not spent. The reason is stored in the audit log.

Breaking change — token is mandatory. Teams automations built against earlier versions must start reading it from the card’s queryproxy envelope and sending it, or their calls will be refused. Requests announced before this version have no token at all: decide those in the web UI.

The signature is computed over "{timestamp}:{raw_body}" (the same timestamped scheme as Slack), base64-encoded, in the Authorization: HMAC … header. A Power Automate flow can compute this header directly.

The HMAC key is the secret as stored, unless the secret is valid base64: then QueryProxy decodes it and signs with the decoded bytes (the convention Teams outgoing webhooks use). Most purely alphanumeric strings are valid base64, so sign with the decoded bytes on your side — or include a character outside the base64 alphabet (such as - or !) to have the secret used as-is.

Security

  • The X-QueryProxy-Timestamp header is required and must be within ±5 minutes of server time, so a captured request cannot be replayed later.
  • An invalid or missing HMAC (or a stale/absent timestamp) returns 401 and changes nothing.
  • The action token makes every call answer one specific announcement, once. The HMAC alone proves that the caller holds the shared secret — never which person is acting — which is also why the secret is administrator-only.
  • The approver must be pre-linked through the Teams ID mapping and must be a member of the integration’s team (or a system admin) — the shared secret alone cannot act as an arbitrary user.
  • Responses are JSON ({"ok": true|false, "message": "…"}); authorization failures return 403 with the reason, an invalid token or unlinked identity returns 422, and an unknown request id or one outside the integration’s team returns 404.
  • The endpoint is rate-limited like every webhook in QueryProxy.

Reminders and expired buttons

A request that stays pending for QUERYPROXY_PENDING_REMIND_AFTER hours (default 4) gets one reminder, with a fresh action token, for the channel and in the portal. Requests never expire. A decision call that carries an expired token answers that the action has expired and points to the newer message.

Full interactive Adaptive Card buttons (as with Slack) require a Teams bot registration and are on the roadmap; the action endpoint covers approvals-from-Teams today.

Arrow keys to move, Enter to open.