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

# Payload schemas

> One flat JSON object per delivery. No envelope, no data wrapper.

The resource's own fields, with an `event` key merged in at the top level. That means you parse a webhook body differently from a list response elsewhere in this API, which does wrap its payload. `event` is always present and always matches one of the 12 names in the [event catalogue](/api-reference/webhooks/events).

## Conversation events

`conversation_created`, `conversation_updated`, and `conversation_status_changed` all share the same conversation shape. `updated` and `status_changed` additionally carry `changed_attributes`.

```json Conversation payload theme={null}
{
  "event": "conversation_status_changed",
  "id": 42,
  "inbox_id": 7,
  "status": "resolved",
  "priority": null,
  "additional_attributes": {},
  "custom_attributes": {},
  "labels": ["billing"],
  "channel": "Channel::Api",
  "can_reply": true,
  "unread_count": 0,
  "snoozed_until": null,
  "waiting_since": 0,
  "first_reply_created_at": 1727654321,
  "agent_last_seen_at": 1727654321,
  "contact_last_seen_at": 1727654300,
  "last_activity_at": 1727654321,
  "timestamp": 1727654321,
  "created_at": 1727650000,
  "updated_at": 1727654321.123,
  "contact_inbox": { "source_id": "abc-123" },
  "messages": [ { "content": "Thanks, that's resolved.", "message_type": 0 } ],
  "meta": {
    "sender": { "id": 9, "name": "Jane Doe", "type": "contact" },
    "assignee": { "id": 3, "name": "Agent Smith" },
    "team": null,
    "hmac_verified": false
  },
  "changed_attributes": [
    { "status": { "previous_value": "open", "current_value": "resolved" } }
  ]
}
```

`id` here is the conversation's **display ID** — the same number used in `/conversations/{id}` path params, not an internal primary key you'd need to translate.

`conversation_updated` fires for any attribute change; `conversation_status_changed` fires specifically when `status` changes, and always carries a `status` entry in `changed_attributes`. `conversation_created` has no `changed_attributes` key at all.

## Message events

`message_created` and `message_updated` — subject to the [incoming/outgoing/template gate](/api-reference/webhooks/events) — carry the message plus its account, inbox, and full conversation nested inside:

```json Message payload theme={null}
{
  "event": "message_created",
  "id": 501,
  "content": "Thanks for reaching out — looking into it now.",
  "content_type": "text",
  "content_attributes": {},
  "message_type": "outgoing",
  "private": false,
  "source_id": null,
  "created_at": 1727654321,
  "account": { "id": 1, "name": "Acme Inc" },
  "inbox": { "id": 7, "name": "Support" },
  "sender": { "id": 3, "name": "Agent Smith", "email": "agent@acme.com", "type": "user" },
  "conversation": { "...": "same shape as the conversation payload above" }
}
```

`attachments` is only present at all if the message has any — it's an array of attachment objects, omitted entirely rather than sent empty.

## Contact and inbox events

`contact_created`, `contact_updated`, `inbox_created`, and `inbox_updated` follow the same pattern as each other: the resource's own fields, flattened, plus `changed_attributes` on the `_updated` variant.

<Warning>
  **`contact_updated` and `inbox_updated` don't fire on a no-op update.** If the update request didn't actually change any tracked attribute, there's no webhook — the listener checks for a non-empty `changed_attributes` before dispatching at all. Don't rely on an update webhook as proof a `PATCH` request was received; rely on it as proof something changed.
</Warning>

```json Contact payload theme={null}
{
  "event": "contact_updated",
  "id": 88,
  "name": "Jane Doe",
  "email": "jane@example.com",
  "phone_number": "+15551234567",
  "identifier": "cust_9021",
  "additional_attributes": {},
  "custom_attributes": { "plan": "pro" },
  "blocked": false,
  "avatar": "https://app.tied.com/rails/active_storage/...",
  "account": { "id": 1, "name": "Acme Inc" },
  "changed_attributes": [
    { "email": { "previous_value": "j@example.com", "current_value": "jane@example.com" } }
  ]
}
```

Note the field name difference: the contact payload's avatar field is `avatar`, not `thumbnail` — the two other resources here (conversations, contact inboxes) use `thumbnail`. This isn't a typo you should normalize away; it's just how each object's own `webhook_data` method names it.

Inbox payloads carry configuration, not just identity — `greeting_message`, `working_hours`, `auto_assignment_config`, `csat_survey_enabled`, and the associated `channel` object, alongside `created_at`/`updated_at`.

## `webwidget_triggered`

Carries the contact\_inbox context plus whatever `event_info` the widget attached:

```json Web widget payload theme={null}
{
  "event": "webwidget_triggered",
  "id": 205,
  "source_id": "widget-session-abc",
  "contact": { "id": 88, "name": "Jane Doe" },
  "inbox": { "id": 7, "name": "Support" },
  "account": { "id": 1, "name": "Acme Inc" },
  "current_conversation": null,
  "event_info": {}
}
```

`current_conversation` is `null` until this contact\_inbox has an actual conversation — expect it on a returning visitor, not a first pageview.

## Typing events

`conversation_typing_on` and `conversation_typing_off` are the one pair that doesn't go through a `webhook_data` merge — they're a small, purpose-built object:

```json Typing payload theme={null}
{
  "event": "conversation_typing_on",
  "user": { "id": 3, "name": "Agent Smith", "email": "agent@acme.com", "type": "user" },
  "conversation": { "...": "same shape as the conversation payload above" },
  "is_private": false
}
```

## Next

<CardGroup cols={2}>
  <Card title="Signature verification" icon="shield-check" href="/api-reference/webhooks/signatures">
    Before you trust any of this: what actually proves it came from Tied.
  </Card>

  <Card title="Retries and testing" icon="refresh-cw" href="/api-reference/webhooks/testing">
    What happens when your endpoint doesn't respond in time.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.