Interactions & Webhooks

Interactions are events fired when users interact with a channel — clicking buttons, changing inputs, or triggering header actions. Outerfaced logs every interaction and optionally delivers a webhook payload to your configured URL.

How Interactions Work

User clicks button in channel UI
        │
        ▼
Interaction is logged
        │
        └─ If webhook_url set → POST payload to webhook_url

Interaction Types

Type Trigger
button_click User clicks a button block
header_action User clicks a header action button
input_change User changes an input/select/toggle with send_on_change: true
reply User submits a text reply in an interactive channel

Webhook Payload

When an interaction occurs on a channel with a webhook_url, Outerfaced sends a POST request with the following JSON body:

{
  "interaction_id": "7bec44fd-ebf5-4fd7-8f22-d8f63fa44ed1",
  "channel_id": "92b65844-0252-4020-998e-86572e301cdb",
  "card_id": "dadef981-5a64-4d4c-a7fa-9ebd6ce2f380",
  "timestamp": "2026-05-15T15:13:31.348041+00:00",
  "trigger": { ... },
  "card_state": [ ... ]
}

Top-level Fields

Field Type Description
interaction_id UUID Unique ID of this interaction event
channel_id UUID Channel where the interaction occurred
card_id UUID | null Card that was interacted with. null for header actions
timestamp ISO 8601 UTC timestamp of when the interaction occurred
trigger object What triggered the webhook — the element, its type, and relevant metadata
card_state array | null Full state of every block on the card at the moment of the trigger. null for header actions

The `trigger` Object

The trigger object describes exactly what the user did. Its shape depends on the interaction type.

`button_click`

Fired when a user clicks any button on a card.

{
  "trigger": {
    "type": "button_click",
    "button_id": "btn_approve",
    "label": "Approve"
  }
}
Field Type Description
type string Always "button_click"
button_id string The id you assigned to this button in the block definition
label string The visible label of the button that was clicked

`header_action`

Fired when a user clicks one of the channel's header action buttons (not on a card).

{
  "trigger": {
    "type": "header_action",
    "label": "Refresh"
  }
}
Field Type Description
type string Always "header_action"
label string The label of the header action button

`reply`

Fired when a user submits a text reply in an interactive channel. Replies are not tied to a specific card, so card_id and card_state will both be null.

{
  "trigger": {
    "type": "reply",
    "text": "I'll take care of this now."
  }
}
Field Type Description
type string Always "reply"
text string The message the user submitted

`input_change`

Fired when a block with send_on_change: true is modified. By default, interactive block values are not sent in real time — they're captured via card_state on button click instead. Enable send_on_change on a specific block to receive live updates.

{
  "trigger": {
    "type": "input_change",
    "block_type": "toggle",
    "value": true
  }
}
Field Type Description
type string Always "input_change"
block_type string The block type that changed: "input", "select", "multiselect", or "toggle"
value string | string[] | boolean Current value of the block after the change

The `card_state` Array

card_state is an ordered array of every block on the card, with its current runtime state at the moment the trigger fired. This lets you know the full context of the card — not just what was clicked, but what every field contained.

{
  "card_state": [
    {
      "id": "header-text",
      "type": "text",
      "content": "Approve this request?"
    },
    {
      "id": "notes-input",
      "type": "input",
      "label": "Notes",
      "value": "Looks good to me"
    },
    {
      "id": "priority-select",
      "type": "select",
      "label": "Priority",
      "value": "high"
    },
    {
      "id": "notify-toggle",
      "type": "toggle",
      "label": "Notify team",
      "value": true
    },
    {
      "id": "action-buttons",
      "type": "button",
      "buttons": [
        { "id": "btn_approve", "label": "Approve", "style": "primary" },
        { "id": "btn_reject", "label": "Reject", "style": "danger" }
      ]
    }
  ]
}

Each entry in the array shares a common id and type, then includes block-specific fields:

Interactive blocks (have a `value`)

type value type Description
input string Current text content of the input
select string | null Currently selected option value, or null if nothing selected
multiselect string[] Array of selected option values
toggle boolean true if enabled, false if disabled

Display blocks (no `value`)

type Extra fields Description
text content The text content of the block
badge label, variant Badge label and its style variant
kv rows Array of { key, value } pairs
image url, alt Image URL and optional alt text
button buttons Array of button definitions (each with id, label, style)
divider No extra fields

Tip: reading `card_state` vs using `trigger`

  • Use trigger to know what the user did (which button, which action).
  • Use card_state to know what state the card was in when they did it (form values, toggle states, etc.).

For example, to get the value of a specific toggle after a button click, find it by id in card_state:

// n8n / JS example
const state = $json.body.card_state
const notifyToggle = state.find(b => b.id === 'notify-toggle')
const shouldNotify = notifyToggle?.value // true or false

Webhook Logs

Every delivery attempt is logged. View logs in the dashboard under Channel → Logs.

Field Values Description
status "success", "failed" Whether delivery succeeded
response_code integer | null HTTP response code returned by your endpoint
attempts integer Number of delivery attempts made

Webhook Requirements

Your endpoint must:

  • Accept POST requests with Content-Type: application/json
  • Return a 2xx HTTP status to acknowledge receipt
  • Respond within 10 seconds (requests time out after this)

Non-2xx responses are logged as "failed". Outerfaced does not automatically retry failed deliveries.

Setting a Webhook URL

Set the webhook_url when creating or updating a channel:

curl -X PATCH /api/v1/channels/{channelId} \
  -H "Authorization: Bearer ofd_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://hook.make.com/your-scenario-id"
  }'

Compatible with Make, Zapier, n8n, and any HTTP webhook receiver. When using n8n, your actual payload is under the body key — reference fields as $json.body.trigger, $json.body.card_state, etc.