> ## Documentation Index
> Fetch the complete documentation index at: https://inkbox.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Slack webhooks

> Subscribe to Slack messages, reactions, membership, files, pins, connection changes, send outcomes, interactions, and session-stop events

Slack events use the shared [Webhook Subscriptions API](/docs/api/webhooks/subscriptions). Set `agent_identity_id` to the owning identity. Select `slack.*` event types alone or mix them with other notification types on the same identity-owned subscription.

## Event types

| Event | Meaning |
| :- | :- |
| `slack.dm_received` | A direct message was received. |
| `slack.group_dm_received` | A group direct message was received. |
| `slack.channel_message_received` | A channel message was received. |
| `slack.mention_received` | A message mentioning the agent was received. |
| `slack.thread_reply_received` | A thread reply was received. |
| `slack.message_updated` | A message changed. |
| `slack.message_deleted` | A message was deleted. |
| `slack.reaction_added` | A reaction was added to an item. |
| `slack.reaction_removed` | A reaction was removed from an item. |
| `slack.member_joined` | A member joined a conversation. |
| `slack.member_left` | A member left a conversation. |
| `slack.channel_updated` | Channel details or lifecycle changed, including topic or purpose. |
| `slack.file_shared` | A file was shared. |
| `slack.file_changed` | A file changed. |
| `slack.file_deleted` | A file was deleted. |
| `slack.pin_added` | An item was pinned. |
| `slack.pin_removed` | An item was unpinned. |
| `slack.connection_changed` | Workspace connection status or availability changed. |
| `slack.message_sent` | Slack accepted an outgoing message. |
| `slack.message_send_failed` | An outgoing send failed with a known outcome. |
| `slack.message_send_unknown` | An outgoing send's outcome could not be established. |
| `slack.interaction` | Supported interaction input was received. |
| `slack.session_stopped` | A session-stop signal was received. |

Availability depends on Slack permissions and the events the app receives. Subscribing does not grant new conversation access. There are no Slack delivered or read event types.

## Register a subscription

```bash cURL theme={null}
curl -X POST "https://inkbox.ai/api/v1/webhooks/subscriptions" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_identity_id": "11111111-1111-4111-8111-111111111111",
    "url": "https://example.com/hooks/slack",
    "event_types": [
      "slack.dm_received",
      "slack.mention_received",
      "slack.connection_changed",
      "slack.message_send_unknown"
    ]
  }'
```

Creation returns `201` with the [subscription object](/docs/api/webhooks/subscriptions#subscription-object). Capture any one-time signing key returned during creation and verify deliveries using the identity's [signing key](/docs/signing-keys).

An identity can have separate subscriptions for different destinations. At one exact destination URL, the same identity and event type can appear on only one active subscription. Overlapping registration normally returns `409`.

A Slack-only create using `agent_identity_id` can instead reuse an existing mixed subscription. Omit `auth_token` and `context_config`. Exactly one active subscription for that identity and exact URL must overlap the requested events, and it must already mix event families. The `201` response returns its existing ID, adds only missing events, and preserves its other events and settings. Ambiguous matches, a single-family existing subscription, or a create that specifies authentication or context still return `409` on overlap. This [compatibility behavior](/docs/api/webhooks/subscriptions#reuse-a-mixed-subscription) does not make all creates idempotent. Updating or deleting the returned mixed subscription still requires `scope=identity`.

A subscription may store `context_config`, but it enriches only received email, text, and iMessage events. Slack events ignore it. Fetch live or retained Slack history explicitly when your runtime needs it.

## Select incoming messages

Choose the incoming event types you need directly in `event_types`. There is no separate Slack filter. Selections cover all accessible conversations across the identity's connected workspaces. Subscribing does not grant access to additional conversations.

A message can match several incoming types. For example, a mention in a channel thread matches `slack.mention_received`, `slack.thread_reply_received`, and `slack.channel_message_received`. Inkbox creates only one delivery per subscription for that message, with a stable event `id`.

The delivered `event_type` is one of the matching types selected by that subscription. If several selected types match, the priority is:

1. `slack.mention_received`
2. `slack.thread_reply_received`
3. `slack.dm_received`
4. `slack.group_dm_received`
5. `slack.channel_message_received`

Selecting only `slack.channel_message_received` still delivers a channel mention under that selected type. Selecting all three matching types delivers it as `slack.mention_received`, without additional thread or channel deliveries.

`slack.thread_reply_received` covers any received reply whose timestamp differs from its thread root. It does not remember which threads your runtime watches. Edits and deletions use their own `slack.message_updated` and `slack.message_deleted` selections, without message-kind restrictions.

### Change event selection

```text theme={null}
PATCH /api/v1/webhooks/subscriptions/{sub_id}?scope=identity
```

Send `event_types` to replace the complete selection. Omit it to preserve the current selection. Include any other notification types you want to keep:

```json theme={null}
{
  "event_types": ["slack.dm_received", "slack.mention_received", "message.received"]
}
```

A mixed subscription requires `scope=identity` for updates or deletion. Without it, the server returns `409` without changing the subscription. Use the same scope when listing all subscriptions for an identity.

The shared webhook catalog includes all 23 Slack event types.

## Payload

```json theme={null}
{
  "id": "evt_example_slack_message",
  "event_type": "slack.mention_received",
  "timestamp": "2026-09-16T10:00:00Z",
  "data": {
    "identity_id": "11111111-1111-4111-8111-111111111111",
    "connection_id": "22222222-2222-4222-8222-222222222222",
    "workspace_id": "T0123456789",
    "conversation_id": "C0123456789",
    "message_ts": "1789552800.000100",
    "thread_ts": null,
    "actor_id": "U0987654321",
    "actor_profile": {
      "id": "U0987654321",
      "real_name": "Example Person",
      "profile": {"display_name": "Example", "email": "person@example.com"}
    },
    "contact_id": "33333333-3333-4333-8333-333333333333",
    "message_kinds": ["channel", "mention"],
    "event": {
      "type": "message",
      "text": "<@U0123456789> Can you check the project notes?",
      "user": "U0987654321",
      "ts": "1789552800.000100"
    }
  }
}
```

| Field | Meaning |
| :- | :- |
| `id` | Stable logical event ID; use it for deduplication across delivery attempts |
| `event_type` | One of the 23 event types above; incoming-message types follow the selected-event priority |
| `timestamp` | Event timestamp |
| `data.identity_id` | Owning Inkbox identity UUID |
| `data.connection_id` | Workspace connection UUID; use it for follow-up API calls |
| `data.workspace_id` | Slack workspace ID |
| `data.conversation_id` | Conversation ID when available, otherwise null |
| `data.message_ts` | Referenced message timestamp when available, otherwise null |
| `data.thread_ts` | Thread root when available, otherwise null |
| `data.actor_id` | Acting Slack user when available, otherwise null |
| `data.actor_profile` | Optional [user profile](/docs/api/slack/users#list-users) for the actor; may be absent or null |
| `data.contact_id` | Optional linked organization contact UUID, subject to contact visibility |
| `data.message_kinds` | Optional descriptive metadata (`dm`, `group_dm`, `channel`, `mention`, or `thread`); can be empty and is not a subscription filter |
| `data.event` | Event-specific content and references, not enriched history |

Message webhooks include available sender details without requiring a separate lookup. Treat `actor_profile` and its fields as optional: profile retrieval or Slack permissions may leave only `actor_id`. Email additionally requires `users:read.email`. Native Slack profiles use the connection's Slack permissions independently of Inkbox contact visibility. Missing enrichment does not prevent the message from arriving.

### Automatic contacts

Incoming activity from a human participant can link that Slack account to an existing organization contact or create a shared, unreviewed contact. This is encounter-driven: listing users or channel members does not import them, and Inkbox does not copy the whole workspace directory.

Inkbox uses an available, Slack-confirmed email for automatic matching. Missing or unconfirmed email produces a Slack-only contact rather than matching another person by that address. Once an account is linked, its workspace and user IDs provide the stable association; an email change does not automatically reassign an established link. A contact can have accounts in multiple workspaces.

New unreviewed contacts can include the available display name, first name, last name, job title, and confirmed email. Existing curated values are preserved. Profile phone numbers, photos, and status remain available through the Slack profile; a profile phone number is not automatically added as an active contact phone number. Review and add a phone number explicitly if needed.

New contacts are shared within the organization by default and marked unreviewed. Contact visibility and Profile access still control `contact_id` and linked accounts in contact reads; an email match grants no additional access or communication permissions. Use `contact_id` with the [Contacts API](/docs/api/contacts/manage#get-contact) when present.

Event content varies by type. Message events include available message content. Deletion events identify the deleted timestamp without promising the deleted text. Reaction and pin events include item references. File events can include a `file_id` and metadata; fetch [file content](/docs/api/slack/files) separately. Send-outcome events include action details for correlation with the [message API](/docs/api/slack/messages).

Connection changes include `event.status`. A Slack rate-limit notice can use `slack.connection_changed` with `reason: "rate_limited"` while the connection remains `connected`; it does not mean the app was disconnected.

## Delivery and runtime behavior

Verify the signature against the raw request body, then deduplicate using `id`. Return a successful HTTP status after durably accepting the event. The response body does not control Slack behavior.

Deliveries can arrive more than once or out of order, including events from the same conversation. Do not infer conversation order from webhook arrival order.

Retries are bounded in time and attempts; delivery is not guaranteed indefinitely. Events received while an identity is paused do not produce queued wakes for later resume. Use retained history for recovery when needed.

Retries keep the same logical event ID. Overlapping message and app-mention notifications for one post resolve to one logical event. Matching more than one message kind does not create additional deliveries for a subscription. Your own bot's message echoes are not inbound message events; use send-outcome events to track its sends.

An app-mention notification that arrives shortly after the ordinary message can identify the same message as a mention. Inkbox can then deliver it to an existing `slack.mention_received` subscription that did not match the first notification, without delivering it again to a subscription that already matched or changing that delivery's selected event type. This enrichment window is 60 seconds; it is not historical replay for a newly created or changed subscription.

Message capture is independent of subscription matching. Restricting wakes to mentions does not restrict retained history to mentions. Paused identities can continue capture without webhook wake delivery.

Your runtime owns wake rules, watched-thread state, follow-up behavior, and memory. A broad subscription is not a request for Inkbox to wake the agent on every event. Decide what matters after receiving the event, and fetch only the history needed for that decision.

Inkbox receives configured interaction callbacks; its public API does not create slash commands, shortcuts, interactive message layouts, or views. Subscribing to `slack.interaction` alone does not make those interfaces available. They must already be configured and sent by the Slack app.

`slack.interaction` carries supported action, shortcut, view, or command inputs. `slack.session_stopped` carries a stop signal and any available thread coordinates. Your runtime must interpret these signals and perform any action or cancellation. Receipt alone does not run commands or stop work.

## Diagnostics and replay

Slack [delivery diagnostics](/docs/api/webhooks/deliveries) expose event coordinates and delivery status, not the original message body. Do not use the delivery log as message history.

A diagnostic with `response_status: null` and
`error_detail: "Webhook delivery could not be prepared"` means Inkbox could not
prepare that delivery; it does not mean your endpoint was contacted. A receiver
failure instead uses `Webhook endpoint did not acknowledge delivery`. Events that
expire before an attempt are not represented as fictitious receiver attempts, so
the delivery log is not a complete event ledger.

Historical Slack webhook replay is unsupported. A replay request returns `422`. Reconnection does not backfill missed events. Use live conversation reads or the [retained archive and explicit backfills](/docs/api/slack/archive) for context, subject to the connection’s remaining access. Backfill imports history; it does not replay old webhook wake events.
