Skip to content

feat(slack): parse interactivity payloads and slash commands - #1540

Draft
cosmicBboy wants to merge 1 commit into
mainfrom
nielsb/slack-plugin-webhook-gaps
Draft

feat(slack): parse interactivity payloads and slash commands#1540
cosmicBboy wants to merge 1 commit into
mainfrom
nielsb/slack-plugin-webhook-gaps

Conversation

@cosmicBboy

Copy link
Copy Markdown
Collaborator

Why

The Slack provider only understood Events API JSON, but a real Slack app receives two more delivery shapes signed with the same v0 HMAC:

  • interactivity payloads (Block Kit actions, shortcuts, modals) — application/x-www-form-urlencoded with the JSON under a payload field
  • slash commands — form-encoded fields

Internal agents like Cally (@app.action("send_to_customer"), /hi-cally) and Nodey (/hi-nodey, app_mention) need all three shapes; today they run hand-rolled slack_bolt servers partly because the plugin 400s on anything form-encoded. This closes that gap.

What

  • parse() sniffs form-encoded bodies and normalizes both shapes:
    • a block action registers as block_actions.<action_id> (e.g. on_event("block_actions.send_to_customer")), a shortcut/message action as its callback_id, a modal as view_submission.<callback_id>
    • a slash command registers as command.<name> (e.g. on_event("command.hi-nodey") for /hi-nodey)
    • the full Slack JSON — actions, container, message, response_url — rides in event.payload, which is everything Cally's handlers read today
  • dedupe: interactions key on the container message plus action_ts (unique per click), so a Slack redelivery is a no-op but two clicks of one button are two events; slash commands key on trigger_id
  • handshake() now also answers the ssl_check probe Slack sends to interactivity and slash-command Request URLs, so those fields verify themselves like url_verification already did
  • new events.Interaction and events.Command constants; README documents pointing Event Subscriptions, Interactivity & Shortcuts, and each slash command at the same /webhook/slack route
  • verify() unchanged — the v0 HMAC signs raw bytes regardless of encoding (test added proving form bodies verify)

Validation

  • 12/12 plugin tests pass, including the shared provider conformance harness
  • end-to-end through WebhookAppEnvironment + FastAPI TestClient with signed, Cally/Nodey-shaped deliveries: block action dispatched to its block_actions.send_to_customer handler, /hi-nodey to its command.hi-nodey handler, ssl_check answered 200, JSON app_mention path unchanged, unsigned delivery refused with 401
  • ruff clean; pre-commit fmt/mypy/ty passed

Notes

Slack expects interactivity/command acks within 3 seconds; handlers should do nothing slower than run_once.aio and post progress back via slack_sdk from the launched task (README now says so). Replying via response_url from the receiver is deliberately left to handler code.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NwQixBcyR5va6BC75jaQx3

The Slack provider only understood Events API JSON, but a real Slack app
receives two more delivery shapes on the same signing scheme: interactivity
payloads (Block Kit actions, shortcuts, modals -- form-encoded JSON under a
`payload` field) and slash commands (form-encoded fields). Internal agents
like Cally (`@app.action("send_to_customer")`) and Nodey (`/hi-nodey`)
could not move onto the plugin without them: those deliveries failed in
`parse` with a 400.

- parse() now sniffs form-encoded bodies and normalizes both shapes: a
  block action registers as `block_actions.<action_id>`, a shortcut as its
  callback_id, a slash command as `command.<name>`; the full Slack JSON
  (actions, container, message, response_url) rides in event.payload
- interactions dedupe per click via action_ts/trigger_id, so a Slack
  redelivery is a no-op but two clicks of one button are two events
- handshake() answers the `ssl_check` probe Slack sends to interactivity
  and slash-command URLs, so those Request URL fields verify themselves
- new events.Interaction and events.Command constants; README documents
  pointing all three Request URLs at the same /webhook/slack route

Verified end to end through WebhookAppEnvironment with signed
Cally/Nodey-shaped deliveries: dispatch, dedupe keys, 401 on unsigned,
and the JSON Events API path unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwQixBcyR5va6BC75jaQx3
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.

1 participant