Webhook Payloads
Every webhook is an HTTP POST request to your Webhook URL with a JSON body. This page describes that body: the envelope that is the same for all 18 events, and every object that can appear inside it.
The request
| Property | Value |
|---|---|
| Method | POST |
| URL | Exactly the Webhook URL saved in Settings -> Webhooks, with no path or query string added |
| Content type | application/json |
| Body | The envelope described below |
| Headers | No signature header, no authentication header and no custom Dead Simple Chat headers are sent |
Because no identifying header is sent, an endpoint that accepts these requests is accepting them from anyone who knows the URL. See Delivery and limitations for how to work around that.
The envelope
The body always has exactly two top-level keys:
{
"event": "new_chatroom_message",
"data": {
"chatMessage": { }
}
}
| Field | Type | Description |
|---|---|---|
event | string | One of the 18 strings listed in the Event reference. Switch on this field. |
data | object | The payload for that event. Its keys depend on the event. |
data holds one object under a named key for most events — chatMessage, channelMessage,
chatRoom, channel, user, likeAction or privateMessage. The one exception is
private_message_initiated, which puts member_one, member_two and chatRoom directly on
data.
Always branch on the event string rather than on the shape of data. Several events share the
same data key, and new event types may be added.
Which identifier each event gives you
Dead Simple Chat has two kinds of room identifier: the public roomId (a short string such as
bbNiVFoXm, the one the REST API uses) and the internal _id (a 24-character hex id). Not every
event carries the public one, and some carry no room identifier at all.
event | Where the room or channel is identified |
|---|---|
chatroom_created, chatroom_updated, chatroom_deleted | data.chatRoom.roomId (public) and data.chatRoom._id |
channel_created, channel_updated, channel_deleted | data.channel.roomId (public) and data.channel._id |
user_joined_chatroom | data.user.lastChatRoomId (public roomId) |
user_left_chatroom | Not included |
new_chatroom_message, chatroom_message_deleted, chatroom_message_approved | data.chatMessage.chatRoom — the internal _id only |
chatroom_message_like_toggle | Not included — only data.likeAction.messageId |
new_channel_message, channel_message_deleted, channel_message_approved | data.channelMessage.channel — the internal channel _id only |
channel_message_like_toggle | data.likeAction.channelId — the internal channel _id only |
private_message_initiated, new_private_message | data.chatRoom / data.privateMessage.chatRoom (public roomId) |
The message events give you the room's internal _id, and the REST API addresses rooms by their
public roomId. The two are different values and one cannot be derived from the other. If you need
to call the REST API in response to a message event, build a lookup of _id to roomId — the
Get All Chat Rooms API returns both.
Objects
chatRoom
Sent by chatroom_created, chatroom_updated and chatroom_deleted. It carries the room's full
current configuration — the same fields the
Get a Chat Room API returns, not only the ones that changed.
The fields most consumers need:
| Field | Type | Description |
|---|---|---|
_id | string | Internal id of the chat room |
roomId | string | Public room id, the one used by the REST API and the embed URL |
name | string | Name of the chat room |
description | string | Description of the chat room |
metadata | string | Your own metadata for the room |
customer | string | Your account id |
chatRoomPermissionLevel | string | public, provisioned_users or members |
created | string | ISO 8601 timestamp |
Because this is the complete record, it also contains the room's configuration — including
roomPassword for a password-protected room, and bannedUsers with the IP addresses of banned
users. Use an https Webhook URL and treat the payload as sensitive.
{
"event": "chatroom_created",
"data": {
"chatRoom": {
"_id": "653f9e50ed0f012c972936fa",
"roomId": "bbNiVFoXm",
"name": "Support Room",
"description": "Chat Room created using the API",
"customer": "631794a83671700c713b1ce5",
"chatRoomPermissionLevel": "public",
"passwordProtected": false,
"preModeratedChatRoom": false,
"enableChannels": true,
"enableOneToOneChat": true,
"created": "2026-09-02T10:14:08.213Z",
"__v": 0
}
}
}
channel
Sent by channel_created, channel_updated and channel_deleted. Unlike chatRoom, this is a
fixed set of five fields, not the whole channel record:
| Field | Type | Description |
|---|---|---|
_id | string | Internal id of the channel. This is the value that appears as channel on a channelMessage. |
channelName | string | Name of the channel |
enabled | boolean | Whether the channel is enabled |
notifyAllUsers | boolean | Whether all users are notified of messages in this channel |
roomId | string | Public roomId of the chat room the channel belongs to |
A channel's metadata is not included, even though channels can store it.
{
"event": "channel_created",
"data": {
"channel": {
"_id": "6540f869ed0f012c9729384b",
"channelName": "announcements",
"enabled": true,
"notifyAllUsers": false,
"roomId": "bbNiVFoXm"
}
}
}
user
Sent by user_joined_chatroom and user_left_chatroom, but the two are not the same shape.
user_joined_chatroom
The full user record for the user who joined. The fields you are most likely to need:
| Field | Type | Description |
|---|---|---|
_id | string | Internal id of the user |
username | string | Display name in the chat room |
lastChatRoomId | string | Public roomId of the room just joined — the only room identifier in this payload |
isModerator | boolean | Whether the user joined as a moderator |
uniqueUserIdentifier | string | Your identifier for a provisioned user, if the user was provisioned |
externalUserId | string | Your external id for the user, if one was set |
meta | string | Your metadata for the user, if any |
profilePic | string | Profile picture URL |
email | string | Email address, if the user has one |
firstName, lastName | string | Set for provisioned users |
parentCustomerAccount | string | Your account id |
ipAddress | string | IP address the user connected from |
created, updated | string | ISO 8601 timestamps |
{
"event": "user_joined_chatroom",
"data": {
"user": {
"_id": "648e211a81cea20bd8b1581d",
"username": "alice",
"uniqueUserIdentifier": "crm-40121",
"isModerator": false,
"claimed": false,
"verified": false,
"profilePic": "https://example.com/avatars/alice.png",
"meta": "{\"tier\":\"gold\"}",
"parentCustomerAccount": "631794a83671700c713b1ce5",
"lastChatRoomId": "bbNiVFoXm",
"lastSeen": "2026-09-02T11:02:44.019Z",
"created": "2026-08-14T09:31:10.442Z",
"updated": "2026-09-02T11:02:44.019Z",
"__v": 0
}
}
}
user_left_chatroom
A reduced object with only these five fields:
| Field | Type | Description |
|---|---|---|
_id | string | Internal id of the user |
username | string | Display name |
email | string | Email address, if any |
isModerator | boolean | Whether the user was a moderator |
parentCustomerAccount | string | Your account id |
There is no lastChatRoomId and no room reference of any kind, so this event cannot tell you which
room was left. Match on _id against the user_joined_chatroom event you stored earlier.
{
"event": "user_left_chatroom",
"data": {
"user": {
"_id": "648e211a81cea20bd8b1581d",
"username": "alice",
"isModerator": false,
"parentCustomerAccount": "631794a83671700c713b1ce5"
}
}
}
chatMessage
Sent by new_chatroom_message, chatroom_message_deleted and chatroom_message_approved.
| Field | Type | Description |
|---|---|---|
_id | string | Internal id of the message |
message | string | The message text, after HTML sanitisation |
created | string | ISO 8601 timestamp |
chatRoom | string | Internal _id of the chat room — not the public roomId |
customer | string | Your account id |
user | object | The sender, in the same shape as the user object above |
likes | array | Reactions on the message. Empty on a new message. |
disapproved | bool | Present and true while a message is held in a pre-moderated chat room. Absent once approved. |
file | object | Present when the message has an attachment: _id, url, fileName, size, fileType, contentType, imageWidth, imageHeight |
repliedTo | object | Present when the message is a reply: message, username, userId, messageId, messageCreatedAt |
poll | string | Id of the poll, when the message created one |
systemMessage | bool | true for messages sent as system messages through the API |
messageOnlyVisibleToUser | string | Set when the message is visible to a single user only |
{
"event": "new_chatroom_message",
"data": {
"chatMessage": {
"_id": "632a11a5f0357f1c648791eb",
"likes": [],
"created": "2026-09-02T11:04:12.848Z",
"chatRoom": "653f9e50ed0f012c972936fa",
"message": "Has anyone seen the release notes?",
"user": {
"_id": "648e211a81cea20bd8b1581d",
"username": "alice",
"isModerator": false,
"parentCustomerAccount": "631794a83671700c713b1ce5",
"lastChatRoomId": "bbNiVFoXm"
},
"customer": "631794a83671700c713b1ce5",
"__v": 0
}
}
}
When a message is sent through the Send Chat Room Message API
with a userAccessToken, the user object carries two extra fields, accessTokenId and iat. Read the fields you need by name rather than assuming a fixed
set.
channelMessage
Sent by new_channel_message, channel_message_deleted and channel_message_approved. It is the
same idea as chatMessage with two differences: it identifies a channel instead of a chat
room, and it has no customer field.
| Field | Type | Description |
|---|---|---|
_id | string | Internal id of the channel message |
message | string | The message text, after HTML sanitisation |
created | string | ISO 8601 timestamp |
channel | string | Internal _id of the channel. There is no chat room field. |
user | object | The sender, in the same shape as the user object above |
likes | array | Reactions on the message |
disapproved | bool | Present and true while held in a pre-moderated chat room |
file | object | Present when the message has an attachment, same fields as above |
repliedTo | object | Present when the message is a reply, same fields as above |
{
"event": "new_channel_message",
"data": {
"channelMessage": {
"_id": "6540f961ed0f012c972938a1",
"likes": [],
"created": "2026-09-02T11:06:55.104Z",
"channel": "6540f869ed0f012c9729384b",
"message": "Deploy is finished.",
"user": {
"_id": "648e211a81cea20bd8b1581d",
"username": "alice",
"isModerator": true,
"parentCustomerAccount": "631794a83671700c713b1ce5"
},
"__v": 0
}
}
}
likeAction
Sent by chatroom_message_like_toggle and channel_message_like_toggle. It describes one
reaction being added or removed — it is not the message and not the full list of reactions.
| Field | Type | Description |
|---|---|---|
messageId | string | Internal id of the message that was reacted to |
reaction | string | One of wave, thumbs_up, clap, pray, strong, celebrate, heart |
action | string | liked when the reaction was added, unliked when it was removed |
username | string | Display name of the user who reacted |
userId | string | Internal id of the user who reacted |
channelId | string | channel_message_like_toggle only. Internal _id of the channel the message is in. |
{
"event": "chatroom_message_like_toggle",
"data": {
"likeAction": {
"messageId": "632a11a5f0357f1c648791eb",
"reaction": "thumbs_up",
"username": "bob",
"userId": "648e2dfe81cea20bd8b15901",
"action": "liked"
}
}
}
private_message_initiated
This event does not nest its payload under a named key. member_one, member_two and
chatRoom sit directly on data.
| Field | Type | Description |
|---|---|---|
member_one | object | The user who opened the conversation |
member_two | object | The other participant |
chatRoom | string | Public roomId of the chat room the conversation is in |
Both member objects have the same five fields:
| Field | Type | Description |
|---|---|---|
_id | string | Internal id of the user |
username | string | Display name |
externalUserId | string | Your external id for the user, if set |
profilePic | string | Profile picture URL |
meta | string | Your metadata for the user, if any |
{
"event": "private_message_initiated",
"data": {
"member_one": {
"_id": "648e211a81cea20bd8b1581d",
"username": "alice",
"externalUserId": "crm-40121",
"profilePic": "https://example.com/avatars/alice.png",
"meta": "{\"tier\":\"gold\"}"
},
"member_two": {
"_id": "648e2dfe81cea20bd8b15901",
"username": "bob",
"profilePic": "https://example.com/avatars/bob.png"
},
"chatRoom": "bbNiVFoXm"
}
}
privateMessage
Sent by new_private_message.
| Field | Type | Description |
|---|---|---|
member_one | object | One participant, in the five-field shape above |
member_two | object | The other participant |
message | string | The message text, after HTML sanitisation |
user | string | Internal id of the sender. Compare it with member_one._id and member_two._id to find out who sent it. |
chatRoom | string | Public roomId of the chat room the conversation is in |
There is no message id, no conversation id and no timestamp on this payload. Use Get Chat Room Conversations and Get Conversation Messages if you need them.
{
"event": "new_private_message",
"data": {
"privateMessage": {
"member_one": {
"_id": "648e211a81cea20bd8b1581d",
"username": "alice",
"externalUserId": "crm-40121",
"profilePic": "https://example.com/avatars/alice.png",
"meta": "{\"tier\":\"gold\"}"
},
"member_two": {
"_id": "648e2dfe81cea20bd8b15901",
"username": "bob",
"profilePic": "https://example.com/avatars/bob.png"
},
"message": "Can you take a look at ticket 214?",
"user": "648e211a81cea20bd8b1581d",
"chatRoom": "bbNiVFoXm"
}
}
}
A minimal handler
app.post("/hooks/deadsimplechat", express.json(), (req, res) => {
// Acknowledge first. A slow response is a lost webhook; there is no retry.
res.sendStatus(200);
const { event, data } = req.body;
switch (event) {
case "new_chatroom_message":
console.log(data.chatMessage.user.username, "said", data.chatMessage.message);
break;
case "user_joined_chatroom":
console.log(data.user.username, "joined room", data.user.lastChatRoomId);
break;
case "channel_created":
console.log("channel", data.channel.channelName, "in room", data.channel.roomId);
break;
default:
console.log("unhandled event", event);
}
});