Skip to main content
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. Message delivery and inbound events use a simpler payload — see below.

CRUD events (contacts / statuses / sources)

EventmodeleventFires when
Contact createdApp\Models\Tenant\ContactcreatedA contact is added
Contact updatedApp\Models\Tenant\ContactupdatedA contact is modified
Contact deletedApp\Models\Tenant\ContactdeletedA contact is removed
Status createdApp\Models\Tenant\StatuscreatedA status is added
Status updatedApp\Models\Tenant\StatusupdatedA status is modified
Status deletedApp\Models\Tenant\StatusdeletedA status is removed
Source createdApp\Models\Tenant\SourcecreatedA source is added
Source updatedApp\Models\Tenant\SourceupdatedA source is modified
Source deletedApp\Models\Tenant\SourcedeletedA 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.
EventmodeleventFires when
Message sentWhatsAppMessagemessage.sentWhatsApp accepted the message from us
Message deliveredWhatsAppMessagemessage.deliveredRecipient’s device received the message
Message readWhatsAppMessagemessage.readRecipient opened the message (only if read receipts enabled on their side)
Message failedWhatsAppMessagemessage.failedDelivery 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:
HeaderValue
Content-Typeapplication/json
X-Webhook-SignatureHMAC-SHA256 of the body using your signing secret
X-Webhook-Evente.g. message.delivered
X-Webhook-TimestampISO 8601 timestamp
User-Agentwhatsmark-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.)
{
  "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

{
  "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

{
  "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

{
  "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"
}
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.
Ordering is best-effort. Network latency means delivered may occasionally arrive before sent, or read before delivered. Compare timestamps if order matters to you.

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.
EventmodeleventFires when
Message receivedWhatsAppMessagemessage.receivedA 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

{
  "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)

{
  "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
FieldTypeNotes
message_idstringWhatsApp wamid of the inbound message.
fromstringSender’s phone (digits, no +). This is also your conversation key.
contact_namestring|nullWhatsApp profile name (may be empty).
typestringtext, image, video, audio, document, interactive (button/list reply), button, reaction, location, contacts.
textstring|nullMessage text, media caption, button-tap title, or reaction emoji. null for media with no caption. For location/contacts, holds a JSON string.
mediastring|nullDirect, downloadable URL for media messages; null otherwise.
media_filenamestring|nullStored filename (reference).
reply_to_message_idstring|nullIf the contact replied to a specific message, the wamid it replies to.
tenant_idintegerYour workspace id.
Enable inbound + delivery webhooks the same way (see Webhooks Overview) — set Webhook URL and turn webhook access on. Inbound events fire automatically; no per-event toggle.
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.

Distinguishing events

Use model + event together. For example, a contact deletion is model: "App\\Models\\Tenant\\Contact" plus event: "deleted".
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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

Webhook Security

Verify every request with the HMAC-SHA256 signature.