message.sent and message.received share the same payload shape: only direction and the presence of user differ. message.delivery_updated reports a change in the delivery status of a message you sent. These are account-level webhooks, delivered to the endpoints you register under Settings → Webhooks. See the overview.
All examples show the full request body, including the envelope. Fields with empty values are omitted from payloads: they are never sent as null. Fields shown in bold are always present. A bold field nested inside an optional object is always present whenever that object is. Everything else is optional.
message.sent
Fires when a message is sent from one of your CloudTalk numbers. The payload carries the complete message (the text, both phone numbers, any media attachments), the contact it went to when one matches, and the user who sent it. Use it to log outbound messages in your CRM or helpdesk automatically, or to trigger follow-up workflows the moment a message goes out.Example
message.received
Fires when a message arrives on one of your CloudTalk numbers. Use it to route inbound messages to your helpdesk or CRM the moment they arrive, or to trigger auto-replies and notifications. The payload has the same shape asmessage.sent, with two differences: direction is inbound, and there is no user; inbound messages have no sending user.
Example
message.delivery_updated
Fires when the delivery status of a message sent from one of your CloudTalk numbers changes: for example when the mobile network confirms the message reached the phone, or reports that it could not be delivered. Use it to retry a failed message, flag a wrong number in your CRM, or alert someone when an important text didn’t arrive. The payload carries the status change only, not the message. Everything else (the text, both numbers, the contact, the user) went out onmessage.sent, so subscribe to both and match them on the message id: message_id here is the same value as id on message.sent.
- Outbound messages only. The event covers messages sent from your CloudTalk numbers. Messages you receive have no delivery status.
- Once per change. The event fires each time the message moves to a new status, so a message usually produces two or three events (for example
queued, thensent, thendelivered). Reports that leave the status unchanged produce no event. - Only forward. A status never moves back: a late report of an earlier status, such as
sentafterdelivered, is ignored.delivered,not_deliveredandfailedare final, except thatseencan followdeliveredon WhatsApp. - Occasionally twice. In rare cases the same status is reported twice for one message. Treat a repeat as a no-op.
- No ordering. Events can reach your endpoint out of order. To find a message’s current status, keep the event with the latest
occurred_atfor thatmessage_id. Don’t rely on arrival order or onstatus_changed_at.
New statuses may be added without a version change. Treat a status you don’t recognize as still in progress.
Error codes
error is present only when status is not_delivered or failed. Branch on error.code. error.message is a sentence written to be shown to your end users; its wording may change, so don’t branch on it.
New codes may be added without a version change. Treat a code you don’t recognize as
unknown.
Fields
message.sent and message.received with channel: "whatsapp", and their delivery status through message.delivery_updated, including seen when the recipient reads the message. Everything else is identical: external_number and internal_number are the two phone numbers, media carries any attachments, and contact and user resolve the same way.
A WhatsApp number that is not enabled for WhatsApp on your account is reported with
channel: "sms". If you see WhatsApp traffic labelled sms, check that WhatsApp is switched on for both the account and the number under Settings → WhatsApp.message.sent means CloudTalk sent the message to the carrier, not that the recipient’s phone received it. To learn whether it arrived, subscribe to message.delivery_updated.