How to Handle Incoming Webhook Payloads
When Carbon Voice POSTs to your webhook endpoint, you'll receive a JSON payload containing message details. This guide explains the key fields you'll need to process incoming messages and send replies.
Normal Payload Shape
Every normal event webhook has this top-level structure:
| Field | Type | Description |
|---|---|---|
eventName | string | The event that triggered this delivery (e.g. "message.posted.to.channel") |
subscribedUserIds | string[] | All subscriber user IDs that are members of the affected resource. Delivered to all subscribers regardless of their OAuth token state. |
subscribedUserSettings | object[] | Per-user locale settings (see below) |
data | object | The event resource (see below) |
subscribedUserSettings fields
Each object in subscribedUserSettings includes:
| Field | Description |
|---|---|
user_id | The subscriber's user ID |
defaultLanguage | The user's preferred language, if set |
timezone | The user's UTC offset in minutes, if set |
data fields
| Field | Description |
|---|---|
resourceId | The ID of the resource that triggered the event |
resourceType | The type of resource (e.g. "Message", "Channel") |
resource | The full resource object (shape varies by event — see Webhook Events Reference) |
All subscribers receive every event
Carbon Voice delivers the normal event to every subscriber regardless of their OAuth token state. There is no token-validity check that gates normal event delivery.
Key Fields inside data.resource
When processing an incoming message.posted.to.channel event, extract these fields from data.resource to use in your reply:
| Field | Use |
|---|---|
conversation_id | The conversation to reply into |
id | The message ID (use as reply_to_message_id to thread your reply) |
transcript_txt | The text of what was said |
creator_id | The user who sent the message (useful for filtering) |
attachments | Array of attached files or links (see below) |
Attachment fields
Each object in attachments includes:
| Field | Description |
|---|---|
id | Attachment ID — use this to request a signed download URL |
type | "file" for uploaded files, "link" for web URLs |
link | For "link" type: a directly accessible URL. For "file" type: an internal storage path — not directly downloadable |
filename | Original filename (file attachments only) |
mime_type | MIME type (file attachments only) |
length_in_bytes | File size in bytes (file attachments only) |
File attachments are not pre-signed. To download a file, exchange its
idfor a short-lived signed URL — see How to Download a File Attachment.
Example Payload Structure
{
"eventName": "message.posted.to.channel",
"subscribedUserIds": ["user-uuid-1", "user-uuid-2"],
"subscribedUserSettings": [
{
"user_id": "user-uuid-1",
"defaultLanguage": "en",
"timezone": -180
},
{
"user_id": "user-uuid-2",
"defaultLanguage": "pt",
"timezone": -180
}
],
"data": {
"resourceId": "message-uuid",
"resourceType": "Message",
"resource": {
"conversation_id": "channel-uuid",
"id": "message-uuid",
"transcript_txt": "Hello, how can I help you?",
"creator_id": "user-uuid-1",
"attachments": []
}
}
}
Common Patterns
Reply to a Message
Use data.resource.conversation_id to identify the conversation and data.resource.id as reply_to_message_id to thread your reply:
curl -X POST https://api.carbonvoice.app/v6/messages/text \
-H "Authorization: Bearer <your-pat>" \
-H "Content-Type: application/json" \
-d '{
"conversation_id": "<data.resource.conversation_id from webhook>",
"transcript": "Here is my response",
"reply_to_message_id": "<data.resource.id from webhook>"
}'
Filter Out Your Own Messages
To prevent infinite reply loops, filter out messages sent by your agent:
if (webhook.data.resource.creator_id === myAgentUserId) {
// Ignore this message, it's from us
return;
}
Or use webhook subscription filters (see How to Register for Webhooks).
Full Payload Reference
For the complete webhook payload structure and all available event types, see: