Skip to content

Add Slack interactivity webhook support - #6674

Open
suhaibmujahid wants to merge 2 commits into
masterfrom
slack-webhook
Open

Add Slack interactivity webhook support#6674
suhaibmujahid wants to merge 2 commits into
masterfrom
slack-webhook

Conversation

@suhaibmujahid

Copy link
Copy Markdown
Member

Resolves #6279

Add a dedicated Slack interactions endpoint with request signature verification.


Stack created with GitHub Stacks CLIGive Feedback 💬

Copilot AI lite review requested due to automatic review settings August 20, 2026 01:49
@suhaibmujahid
suhaibmujahid requested a review from a team as a code owner August 20, 2026 01:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds inbound Slack interactivity support to hackbot-api by introducing a dedicated /slack/interactions endpoint protected with Slack request signature verification, plus parsing of Slack’s form-encoded interaction payloads into a normalized click object.

Changes:

  • Introduces Slack signature verification (require_slack_signature / verify_slack_signature) and wires it into a new Slack router.
  • Adds Slack interaction payload parsing utilities (parse_interaction / parse_payload) and registers the new router in the FastAPI app.
  • Adds slack-sdk dependency and ensures test env defaults include SLACK_SIGNING_SECRET.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
services/hackbot-api/tests/conftest.py Adds dummy SLACK_SIGNING_SECRET so settings validation doesn’t block test imports.
services/hackbot-api/pyproject.toml Adds slack-sdk dependency for signature verification.
services/hackbot-api/app/slack_webhook.py New Slack interaction payload decoding/parsing into a ButtonClick model.
services/hackbot-api/app/routers/slack.py New /slack/interactions endpoint secured by Slack HMAC signature verification.
services/hackbot-api/app/routers/init.py Exposes the new Slack router from the router package.
services/hackbot-api/app/main.py Registers the Slack router on the FastAPI app.
services/hackbot-api/app/config.py Adds Slack settings model and embeds it into global settings.
services/hackbot-api/app/auth.py Implements Slack signature verification + dependency for request authentication.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +64 to +72
def _decode_value(raw: str | None) -> dict[str, Any] | None:
"""The args off a button's ``value``, or None if it is not one of ours."""
if not raw:
return None
try:
decoded = json.loads(raw)
except ValueError:
log.warning("Slack interaction: button value is not JSON")
return None
Comment on lines +96 to +102
actions = payload.get("actions") or []
# A click reports exactly one action even in a block of several buttons, so
# anything past the first would be a payload shape this does not know.
action = actions[0] if actions else None
if not isinstance(action, dict) or not action.get("action_id"):
log.warning("Ignoring Slack %s delivery with no action", BLOCK_ACTIONS)
return None
Comment on lines +1 to +5
"""Inbound Slack interactivity receiver: clicks on the messages hackbot posts.

A message recorded with buttons (``hackbot_runtime.actions.slack.button``) is
posted as Block Kit by the apply step; when someone clicks one, Slack POSTs the
click here. Authenticated by Slack's HMAC signature rather than the ``X-API-Key``
Comment on lines 62 to +65
webhook: WebhookSettings

slack: SlackSettings

Comment on lines +50 to +72
def verify_slack_signature(
raw_body: bytes, timestamp: str | None, signature: str | None
) -> bool:
"""Constant-time-check Slack's `X-Slack-Signature` over the raw request body.

Slack signs `v0:{timestamp}:{body}` with the app's signing secret and sends the
digest as `v0=<hex>` in the header. `slack_sdk`'s verifier does that comparison
and additionally rejects a timestamp more than five minutes from now, which is
what stops a captured delivery from being replayed later. The Phabricator
signature has no such window, so this cannot simply reuse it.

Returns False if the secret is unconfigured or either header is missing or
garbled, so a service without `SLACK_SIGNING_SECRET` rejects every delivery
instead of accepting them all.
"""
secret = settings.slack.signing_secret
if not secret or not timestamp or not signature:
return False
try:
return SignatureVerifier(secret).is_valid(raw_body, timestamp, signature)
except ValueError:
# A non-numeric timestamp header reaches an `int()` inside the verifier.
return False
Add a dedicated Slack interactions endpoint with request signature verification.

@evgenyrp evgenyrp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also ask Claude to document this functionality concisely in /docs/hackbot? This would also help with review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hackbot job deduplication

3 participants