Messages
Messages
Message created
message.createdFires when a new message is created — inbound from a contact or outbound from a user or the system. For ad-originated messages (WhatsApp Click-to-Message and Instagram click-to-Direct ads) the payload includes a referral object with ad details. WhatsApp quick-reply and interactive button taps are normalized into a regular text message: the button label goes to content.text and the button payload to content.interactive_reply_payload.
HTTP request
AISAR sends a signed POST request to your webhook_url:
http
POST {webhook_url}
Content-Type: application/json
X-AISAR-Signature: sha256=...
{
"event": "message.created",
"event_id": "evt_01JXXXXXXXXXXXXXXXXXXXXX",
"timestamp": "2026-03-22T12:00:00.000000Z",
"company_id": 1,
"data": {
"message": {
"id": 123,
"external_id": "3EB0ABC123456789",
"conversation_id": 45,
"thread_id": 67,
"channel_id": 2,
"direction": "inbound",
"type": "text",
"content": {
"text": "Hello, I need help"
},
"display_content": "Hello, I need help",
"media": null,
"reply_to_message_id": null,
"is_deleted": false,
"system_notification": false,
"created_at": "2026-03-22T12:00:00.000000Z"
},
"sender": {
"type": "contact",
"contact_id": 10,
"contact_account_id": 15,
"name": "John Doe",
"phone": "+77001234567",
"email": "john@example.com",
"external_id": "77001234567@s.whatsapp.net"
},
"recipient": null,
"conversation": {
"id": 45,
"status": "active",
"contact_id": 10,
"deal_id": null,
"subject": null,
"is_archived": false,
"is_group": false
},
"channel": {
"id": 2,
"type": "whatsapp",
"name": "Main WhatsApp"
},
"is_dialog_assigned": false,
"referral": null
}
}The X-AISAR-Signature header carries an HMAC-SHA256 signature of the request body — use it to verify the request came from AISAR.
Example payload
json
{
"event": "message.created",
"event_id": "evt_01JXXXXXXXXXXXXXXXXXXXXX",
"timestamp": "2026-03-22T12:00:00.000000Z",
"company_id": 1,
"data": {
"message": {
"id": 123,
"external_id": "3EB0ABC123456789",
"conversation_id": 45,
"thread_id": 67,
"channel_id": 2,
"direction": "inbound",
"type": "text",
"content": {
"text": "Hello, I need help"
},
"display_content": "Hello, I need help",
"media": null,
"reply_to_message_id": null,
"is_deleted": false,
"system_notification": false,
"created_at": "2026-03-22T12:00:00.000000Z"
},
"sender": {
"type": "contact",
"contact_id": 10,
"contact_account_id": 15,
"name": "John Doe",
"phone": "+77001234567",
"email": "john@example.com",
"external_id": "77001234567@s.whatsapp.net"
},
"recipient": null,
"conversation": {
"id": 45,
"status": "active",
"contact_id": 10,
"deal_id": null,
"subject": null,
"is_archived": false,
"is_group": false
},
"channel": {
"id": 2,
"type": "whatsapp",
"name": "Main WhatsApp"
},
"is_dialog_assigned": false,
"referral": null
}
}Payload fields
| Field | Type | Description |
|---|---|---|
message.id | integer | Internal message ID |
message.external_id | string | External message ID (platform-specific, e.g. WhatsApp message ID) |
message.conversation_id | integer | Conversation ID |
message.thread_id | integer | Thread ID |
message.channel_id | integer | Channel ID |
message.direction | string | inbound (from contact) or outbound (from user/system) |
message.type | string | Message type: text, image, video, audio, voice, document, sticker, location, contact, poll, template, ig_post, ig_reel, ig_story, ig_story_reply, story_mention, share, ephemeral, unsupported_type, comment, call, note. voice is a voice note (recorded audio, OGG/Opus); plain audio is an audio file. template, ig_post, ig_reel, ig_story, ig_story_reply, story_mention, share, ephemeral, unsupported_type are Instagram attachment kinds (a generic-template card, a reposted post/reel/story, a story reply, a story mention, a shared post, a self-destructing attachment, and an unrecognized attachment respectively): for these, message.type inherits the type of the message's first attachment (see message.media[].type below) rather than being set independently. comment is a comment on an Instagram/Threads post (a comment thread). call is a call card in the timeline. note is an internal operator note (never leaves AISAR). WhatsApp quick-reply / interactive button taps are normalized to text. Reactions are NOT a message_type — they are delivered via the separate `message.reaction.updated` event, not through `message.created` |
message.content | object|string | Message content. For text: {"text": "..."}. For media it includes caption, mime_type, etc. For button taps it also carries interactive_reply_payload — the button payload / list option id (null when the template button has no payload) |
message.display_content | string|null | Plain text representation of the message content |
message.media | array|null | Attachments array; null if the message has no media. Each item carries only type/url/filename/mime_type/size/duration/playback_url — an attachment's metadata field (download_status, media_id, IG card contents, etc.) is NOT included in the webhook payload; it is available only via REST (GET /messages/{id} or .../attachments) |
message.media[].type | string | Attachment type (the full message_attachments.type CHECK-constraint set): image, video, audio, voice, document, file, contact, location, sticker, ig_reel, ig_post, ig_story, share, ig_story_reply, story_mention, ephemeral, unsupported_type, template |
message.media[].url | string | Download URL |
message.media[].filename | string|null | Original filename |
message.media[].mime_type | string|null | MIME type (e.g. image/jpeg; voice notes are audio/ogg) |
message.media[].size | integer|null | File size in bytes |
message.media[].duration | integer|null | Duration in seconds (audio / voice / video) |
message.media[].playback_url | string|null | A signed link to the m4a/AAC version of a voice attachment (lazily transcoded from the original OGG/Opus). Present only for voice attachments and audio/ogg attachments whose file is already stored locally; null otherwise. In the webhook the link is temporary — valid for 7 days (the REST version of the same field, MessageAttachment.playback_url, never expires) |
message.reply_to_message_id | integer|null | ID of the quoted message, if this is a reply |
message.is_deleted | boolean | Whether the message has been deleted |
message.system_notification | boolean | Whether this is a system notification |
message.created_at | string | ISO 8601 timestamp |
sender.type | string | contact for inbound messages, user for outbound |
sender.contact_id | integer | Contact ID (inbound messages) |
sender.contact_account_id | integer | Contact account ID (inbound messages) |
sender.user_id | integer | User ID (outbound messages) |
sender.name | string | Sender display name |
sender.phone | string|null | Contact phone number (inbound messages) |
sender.email | string|null | Sender email |
sender.external_id | string | Platform-specific contact ID, e.g. 77001234567@s.whatsapp.net (inbound messages) |
recipient | object|null | Present only for outbound messages: the recipient contact, same structure as sender with type=contact |
conversation.id | integer | Conversation ID |
conversation.status | string | active or closed |
conversation.contact_id | integer | Contact ID |
conversation.deal_id | integer|null | Associated deal ID |
conversation.subject | string|null | Conversation subject |
conversation.is_archived | boolean | Whether the conversation is archived |
conversation.is_group | boolean | true if this conversation is a group chat (e.g. a WhatsApp group), false for direct messages |
channel.id | integer | Channel ID |
channel.type | string | Channel type key: `whatsapp`, `whatsapp_business`, `instagram`, `instagram_business`, `threads`, `telegram`, `telegram_bot`, `live_chat` |
channel.name | string | Channel name |
is_dialog_assigned | boolean | true if a user is assigned to this conversation, false otherwise |
referral | object|null | Referral data for ad-originated messages; null for regular messages. Populated on the first message of a conversation started from an ad, for the `whatsapp`, `whatsapp_business` (Click-to-WhatsApp) and `instagram_business` (click-to-Direct ads) channels. The field set depends on the channel: WhatsApp supplies `source_url`, `ctwa_clid`, `body`, `media_type`; Instagram supplies `post_id` and `ref`. Common to both: `source_type`, `source_id`, `headline`, `image_url`, `video_url`. Always null for other channel types. |
referral.source_url | string | URL of the ad or post; WhatsApp only |
referral.source_type | string | Source type: ad or post |
referral.source_id | string | Ad or post ID (`ad_id` for Instagram); can be used with the Meta API to get campaign details |
referral.headline | string | Ad headline text |
referral.body | string | Ad body text |
referral.ctwa_clid | string | Click tracking ID for precise attribution; WhatsApp only |
referral.media_type | string | Media type in the ad: image or video; WhatsApp only |
referral.image_url | string | URL of the ad image (if applicable) |
referral.video_url | string | URL of the ad video (if applicable) |
referral.post_id | string | ID of the post the ad ran from; Instagram only |
referral.ref | string | Arbitrary label from the deep link (`ig.me/...?ref=`) when set; Instagram only |