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

# Webhook Events

> Every outbound webhook event Xobito emits, with full JSON payloads.

Xobito sends outbound webhooks across these groups: **contacts / statuses / sources** CRUD (9 events), **message delivery status** (4 events: sent / delivered / read / failed), and **inbound messages** (`message.received`). CRUD events use the [contact-style payload envelope](/developers/webhooks-overview#payload). Message delivery and inbound events use a [simpler payload](#message-delivery-status-events) — see below.

## CRUD events (contacts / statuses / sources)

| Event           | `model`                     | `event`   | Fires when            |
| --------------- | --------------------------- | --------- | --------------------- |
| Contact created | `App\Models\Tenant\Contact` | `created` | A contact is added    |
| Contact updated | `App\Models\Tenant\Contact` | `updated` | A contact is modified |
| Contact deleted | `App\Models\Tenant\Contact` | `deleted` | A contact is removed  |
| Status created  | `App\Models\Tenant\Status`  | `created` | A status is added     |
| Status updated  | `App\Models\Tenant\Status`  | `updated` | A status is modified  |
| Status deleted  | `App\Models\Tenant\Status`  | `deleted` | A status is removed   |
| Source created  | `App\Models\Tenant\Source`  | `created` | A source is added     |
| Source updated  | `App\Models\Tenant\Source`  | `updated` | A source is modified  |
| Source deleted  | `App\Models\Tenant\Source`  | `deleted` | A source is removed   |

## Message delivery status events

Live as of **2026-05-28**. Fires every time Meta reports a status change for a message you sent via the [Send Message endpoints](/api-reference/endpoints/send-text-message).

| Event             | `model`           | `event`             | Fires when                                                                 |
| ----------------- | ----------------- | ------------------- | -------------------------------------------------------------------------- |
| Message sent      | `WhatsAppMessage` | `message.sent`      | WhatsApp accepted the message from us                                      |
| Message delivered | `WhatsAppMessage` | `message.delivered` | Recipient's device received the message                                    |
| Message read      | `WhatsAppMessage` | `message.read`      | Recipient opened the message (only if read receipts enabled on their side) |
| Message failed    | `WhatsAppMessage` | `message.failed`    | Delivery failed — `error_message` populated                                |

**Mapping back to your record:** the `data.message_id` is the exact same `wamid.*` returned in the `data.whatsapp_response.messages[0].id` field of every successful send response. Keep a `(message_id → your_record_id)` table and match on it.

**Headers for message delivery webhooks:**

| Header                | Value                                             |
| --------------------- | ------------------------------------------------- |
| `Content-Type`        | `application/json`                                |
| `X-Webhook-Signature` | HMAC-SHA256 of the body using your signing secret |
| `X-Webhook-Event`     | e.g. `message.delivered`                          |
| `X-Webhook-Timestamp` | ISO 8601 timestamp                                |
| `User-Agent`          | `whatsmark-Webhook/1.0`                           |

### Example payload — `message.sent`

The first (`sent`) event also includes the outbound message **content** (`text` + `type`) when
available, so you can mirror messages sent from the dashboard — not just ones you sent via the
API. (`delivered` / `read` / `failed` events omit content; you already have it from `sent`.)

```json theme={null}
{
  "event": "message.sent",
  "model": "WhatsAppMessage",
  "data": {
    "message_id": "wamid.HBgMOTE3OTg0OTM5ODcxFQIAERgSOTkxOTdCMjQ1N0JENjc2RjM5AA==",
    "status": "sent",
    "error_message": null,
    "text": "Hi Rahul, your order has shipped.",
    "type": "text",
    "tenant_id": 6
  },
  "timestamp": "2026-05-28T14:50:56+00:00"
}
```

### Example payload — `message.delivered`

```json theme={null}
{
  "event": "message.delivered",
  "model": "WhatsAppMessage",
  "data": {
    "message_id": "wamid.HBgMOTE3OTg0OTM5ODcxFQIAERgSOTkxOTdCMjQ1N0JENjc2RjM5AA==",
    "status": "delivered",
    "error_message": null,
    "tenant_id": 6
  },
  "timestamp": "2026-05-28T14:50:57+00:00"
}
```

### Example payload — `message.read`

```json theme={null}
{
  "event": "message.read",
  "model": "WhatsAppMessage",
  "data": {
    "message_id": "wamid.HBgMOTE3OTg0OTM5ODcxFQIAERgSOTkxOTdCMjQ1N0JENjc2RjM5AA==",
    "status": "read",
    "error_message": null,
    "tenant_id": 6
  },
  "timestamp": "2026-05-28T14:51:27+00:00"
}
```

### Example payload — `message.failed`

```json theme={null}
{
  "event": "message.failed",
  "model": "WhatsAppMessage",
  "data": {
    "message_id": "wamid.HBgM...",
    "status": "failed",
    "error_message": "Recipient phone number not on WhatsApp",
    "tenant_id": 6
  },
  "timestamp": "2026-05-28T14:52:10+00:00"
}
```

<Note>
  **Idempotency:** Meta occasionally retries status webhooks to us, which can result in duplicate POSTs to your URL. Dedupe on your end by the `(message_id, status)` pair — ignore the second if you've already processed it.
</Note>

<Note>
  **Ordering is best-effort.** Network latency means `delivered` may occasionally arrive before `sent`, or `read` before `delivered`. Compare timestamps if order matters to you.
</Note>

## Inbound message events

Fires every time a contact sends a message **to** your WhatsApp number. Lets you mirror the full
two-way conversation on your side.

| Event            | `model`           | `event`            | Fires when                                                                       |
| ---------------- | ----------------- | ------------------ | -------------------------------------------------------------------------------- |
| Message received | `WhatsAppMessage` | `message.received` | A contact sends you any message (text, media, button tap, reaction, location, …) |

**Headers** — same as delivery events (`X-Webhook-Signature`, `X-Webhook-Event: message.received`, `X-Webhook-Timestamp`, `User-Agent`).

### Example payload — text

```json theme={null}
{
  "event": "message.received",
  "model": "WhatsAppMessage",
  "data": {
    "message_id": "wamid.HBgMOTE3OTg0OTM5ODcxFQIAEhgg...",
    "from": "919909919284",
    "contact_name": "Rahul Patel",
    "type": "text",
    "text": "Yes, please send the details",
    "media": null,
    "media_filename": null,
    "reply_to_message_id": null,
    "tenant_id": 6
  },
  "timestamp": "2026-06-01T09:16:53+00:00"
}
```

### Example payload — media (image/document/audio/video)

```json theme={null}
{
  "event": "message.received",
  "model": "WhatsAppMessage",
  "data": {
    "message_id": "wamid.HBgM...",
    "from": "919909919284",
    "contact_name": "Rahul Patel",
    "type": "image",
    "text": "optional caption, or null",
    "media": "https://dash.example.com/storage/whatsapp-attachments/media_6a1d793c09e0b.jpg",
    "media_filename": "media_6a1d793c09e0b.jpg",
    "reply_to_message_id": null,
    "tenant_id": 6
  },
  "timestamp": "2026-06-01T12:21:16+00:00"
}
```

**Fields**

| Field                 | Type         | Notes                                                                                                                                               |
| --------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `message_id`          | string       | WhatsApp `wamid` of the inbound message.                                                                                                            |
| `from`                | string       | Sender's phone (digits, no `+`). This is also your conversation key.                                                                                |
| `contact_name`        | string\|null | WhatsApp profile name (may be empty).                                                                                                               |
| `type`                | string       | `text`, `image`, `video`, `audio`, `document`, `interactive` (button/list reply), `button`, `reaction`, `location`, `contacts`.                     |
| `text`                | string\|null | Message text, media caption, button-tap title, or reaction emoji. `null` for media with no caption. For `location`/`contacts`, holds a JSON string. |
| `media`               | string\|null | Direct, downloadable URL for media messages; `null` otherwise.                                                                                      |
| `media_filename`      | string\|null | Stored filename (reference).                                                                                                                        |
| `reply_to_message_id` | string\|null | If the contact replied to a specific message, the `wamid` it replies to.                                                                            |
| `tenant_id`           | integer      | Your workspace id.                                                                                                                                  |

<Note>
  Enable inbound + delivery webhooks the same way (see [Webhooks Overview](/developers/webhooks-overview)) — set **Webhook URL** and turn webhook access on. Inbound events fire automatically; no per-event toggle.
</Note>

<Warning>
  The following events still do **not** fire outbound webhooks:

  * Campaigns — `campaign.started`, `campaign.completed`, etc.
  * Templates — `template.approved`, `template.rejected`, etc.
  * Tickets and conversations — no outbound webhooks.

  If you need any of these, poll the relevant API endpoint on an interval you control.
</Warning>

## Distinguishing events

Use `model` + `event` together. For example, a contact deletion is `model: "App\\Models\\Tenant\\Contact"` plus `event: "deleted"`.

```javascript theme={null}
function route(payload) {
  const isContact = payload.model.endsWith("\\Contact");
  const isStatus = payload.model.endsWith("\\Status");
  const isSource = payload.model.endsWith("\\Source");

  if (isContact && payload.event === "created") onContactCreated(payload.data);
  if (isContact && payload.event === "updated") onContactUpdated(payload.data);
  if (isContact && payload.event === "deleted") onContactDeleted(payload.data);
  // ... same pattern for status and source
}
```

## Example payloads

### Contact created

```json theme={null}
{
  "event": "created",
  "model": "App\\Models\\Tenant\\Contact",
  "data": {
    "id": 451,
    "attributes": {
      "id": 451,
      "tenant_id": 13,
      "firstname": "John",
      "lastname": "Doe",
      "company": "Demo Co",
      "type": "lead",
      "email": "john@example.com",
      "phone": "+14155551234",
      "source_id": 2,
      "status_id": 1,
      "country_id": 101,
      "group_id": [1, 2],
      "is_enabled": 1,
      "is_opted_out": 0,
      "created_at": "2026-04-16T12:34:56.000000Z",
      "updated_at": "2026-04-16T12:34:56.000000Z"
    },
    "relations": {}
  },
  "original": null,
  "timestamp": "2026-04-16T12:34:56+00:00"
}
```

### Contact updated

```json theme={null}
{
  "event": "updated",
  "model": "App\\Models\\Tenant\\Contact",
  "data": {
    "id": 451,
    "attributes": {
      "id": 451,
      "tenant_id": 13,
      "firstname": "John",
      "lastname": "Doe",
      "email": "john+new@example.com",
      "phone": "+14155551234",
      "type": "customer",
      "status_id": 3,
      "source_id": 2,
      "updated_at": "2026-04-16T13:01:10.000000Z"
    },
    "relations": {}
  },
  "original": null,
  "timestamp": "2026-04-16T13:01:10+00:00"
}
```

### Contact deleted

```json theme={null}
{
  "event": "deleted",
  "model": "App\\Models\\Tenant\\Contact",
  "data": {
    "id": 451,
    "attributes": {
      "id": 451,
      "tenant_id": 13,
      "firstname": "John",
      "lastname": "Doe",
      "phone": "+14155551234"
    },
    "relations": {}
  },
  "original": null,
  "timestamp": "2026-04-16T13:15:22+00:00"
}
```

### Status created

```json theme={null}
{
  "event": "created",
  "model": "App\\Models\\Tenant\\Status",
  "data": {
    "id": 12,
    "attributes": {
      "id": 12,
      "tenant_id": 13,
      "name": "Qualified",
      "color": "#22C55E",
      "isdefault": 0,
      "created_at": "2026-04-16T12:00:00.000000Z",
      "updated_at": "2026-04-16T12:00:00.000000Z"
    },
    "relations": {}
  },
  "original": null,
  "timestamp": "2026-04-16T12:00:00+00:00"
}
```

### Status updated

```json theme={null}
{
  "event": "updated",
  "model": "App\\Models\\Tenant\\Status",
  "data": {
    "id": 12,
    "attributes": {
      "id": 12,
      "tenant_id": 13,
      "name": "Qualified Lead",
      "color": "#16A34A",
      "updated_at": "2026-04-16T12:10:00.000000Z"
    },
    "relations": {}
  },
  "original": null,
  "timestamp": "2026-04-16T12:10:00+00:00"
}
```

### Status deleted

```json theme={null}
{
  "event": "deleted",
  "model": "App\\Models\\Tenant\\Status",
  "data": {
    "id": 12,
    "attributes": { "id": 12, "tenant_id": 13, "name": "Qualified Lead" },
    "relations": {}
  },
  "original": null,
  "timestamp": "2026-04-16T12:20:00+00:00"
}
```

### Source created

```json theme={null}
{
  "event": "created",
  "model": "App\\Models\\Tenant\\Source",
  "data": {
    "id": 7,
    "attributes": {
      "id": 7,
      "tenant_id": 13,
      "name": "Website Form",
      "created_at": "2026-04-16T11:00:00.000000Z",
      "updated_at": "2026-04-16T11:00:00.000000Z"
    },
    "relations": {}
  },
  "original": null,
  "timestamp": "2026-04-16T11:00:00+00:00"
}
```

### Source updated

```json theme={null}
{
  "event": "updated",
  "model": "App\\Models\\Tenant\\Source",
  "data": {
    "id": 7,
    "attributes": {
      "id": 7,
      "tenant_id": 13,
      "name": "Marketing Site",
      "updated_at": "2026-04-16T11:05:00.000000Z"
    },
    "relations": {}
  },
  "original": null,
  "timestamp": "2026-04-16T11:05:00+00:00"
}
```

### Source deleted

```json theme={null}
{
  "event": "deleted",
  "model": "App\\Models\\Tenant\\Source",
  "data": {
    "id": 7,
    "attributes": { "id": 7, "tenant_id": 13, "name": "Marketing Site" },
    "relations": {}
  },
  "original": null,
  "timestamp": "2026-04-16T11:15:00+00:00"
}
```

## Next

<Card title="Webhook Security" icon="shield" href="/developers/webhook-security">
  Verify every request with the HMAC-SHA256 signature.
</Card>
