# Interactivity

A person presses a button on your bot's message or submits its modal, and your app receives Slack's block_actions or view_submission payload.

A person can press a button on your bot's message, or submit your bot's modal, and your app receives Slack's [`block_actions`](https://docs.slack.dev/reference/interaction-payloads/block_actions-payload) or [`view_submission`](https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload) payload. The press is a call the Slack client makes, so it carries `X-Rystic-Surface: client` and the person's identity (a bound token or `X-Rystic-Actor`); without the header both methods answer `unknown_method`, as on the Web API. Post a message with a button, press it as a person and see what the simulator answers when no app is listening:

```bash
curl -s -X POST "$RYSTIC_URL/_rystic/scenario" -d '{"name":"design_studio"}' -o /dev/null
curl -s -X POST "$RYSTIC_URL/api/chat.postMessage" -H 'Authorization: Bearer xoxb-local' --data-urlencode 'blocks=[{"type":"actions","block_id":"review","elements":[{"type":"button","action_id":"approve","text":{"type":"plain_text","text":"Approve"}}]}]' -d channel=CWDC0000004 -d 'text=Review the draft' | jq -c '{ok, ts}'
# -> {"ok":true,"ts":"1772323200.000001"}
curl -s -X POST "$RYSTIC_URL/api/blocks.actions" -H 'Authorization: Bearer xoxp-anything' -d channel=CWDC0000004 -d ts=1772323200.000001 -d action_id=approve | jq -c .
# -> {"error":"unknown_method","ok":false,"req_method":"blocks.actions"}
curl -s -X POST "$RYSTIC_URL/api/blocks.actions" -H 'Authorization: Bearer xoxp-anything' -H 'X-Rystic-Actor: UWDC0000020' -H 'X-Rystic-Surface: client' -d channel=CWDC0000004 -d ts=1772323200.000001 -d action_id=approve | jq -c .
# -> {"error":"app_unreachable","ok":false}
curl -s -X POST "$RYSTIC_URL/api/blocks.actions" -H 'Authorization: Bearer xoxp-anything' -H 'X-Rystic-Actor: UWDC0000020' -H 'X-Rystic-Surface: client' -d channel=CWDC0000004 -d ts=1772323200.000001 -d action_id=reject | jq -c .
# -> {"error":"action_not_found","ok":false}
```

### The two methods

`blocks.actions` takes `channel` and `ts` (the message), `action_id` and optionally `block_id`, and presses that button. `views.submit` takes `view_id` and `state` (`{"values":{"<block_id>":{"<action_id>":{…}}}}`), the modal's inputs.

### Where the payload goes

The app whose `app_id` the message or view carries receives it. Over Socket Mode it is an `interactive` envelope on that app's connections alone. Otherwise it is a signed form post of `payload=<JSON>` to `SLACK_TWIN_INTERACTIVITY_URL`, or to `SLACK_TWIN_EVENTS_URL` when that is unset, since Bolt serves both on one path. Either needs `SLACK_TWIN_SIGNING_SECRET`; without it the simulator refuses to start. Only the default app has a request URL. [Events API](https://www.rystic.ai/docs/simulators/slack/features/events-api.md) covers the two delivery modes.

### Acks and errors

Your app acks within 3 seconds, by `envelope_id` or with a 2xx, and the call answers `{"ok":true}`, with `"ack"` holding what your app sent back, such as a submit's `{"response_action":"errors",…}`. An interaction is never retried.

Otherwise the call answers `app_unreachable`, `app_timeout` or `app_error` (a non-2xx, or an answer cut short or over 1 MiB). A message, button or view that is not there answers `message_not_found`, `action_not_found` or `view_not_found`. These codes are the simulator's own: Slack shows the person a generic error. A `response_url` answers `used_url` once its five calls are spent and `expired_url` after 30 minutes.

### Trigger ids and response URLs

Each interaction carries a `trigger_id` that lives 3 seconds on the simulator's clock and is exchanged once. `views.open` does not take it yet; it still needs a seeded `state_trigger`.

`response_url` is on the simulator's own origin and answers 5 calls within 30 minutes of simulator time, made as your app. `replace_original: true` updates the message, `delete_original: true` deletes it, `response_type: "in_channel"` posts in the channel, and otherwise the reply is ephemeral to the person who pressed, Slack's default. Your app can call `chat.update` or `chat.postEphemeral` with its bot token instead.

### Not modeled

Select menus, shortcuts, slash commands and `views.push` are not modeled, and neither is `view_closed`.

---

Source: https://www.rystic.ai/docs/simulators/slack/features/interactivity · Markdown: https://www.rystic.ai/docs/simulators/slack/features/interactivity.md
