Developer Portal
How-to guides

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:

FieldTypeDescription
eventNamestringThe event that triggered this delivery (e.g. "message.posted.to.channel")
subscribedUserIdsstring[]All subscriber user IDs that are members of the affected resource. Delivered to all subscribers regardless of their OAuth token state.
subscribedUserSettingsobject[]Per-user locale settings (see below)
dataobjectThe event resource (see below)

subscribedUserSettings fields

Each object in subscribedUserSettings includes:

FieldDescription
user_idThe subscriber's user ID
defaultLanguageThe user's preferred language, if set
timezoneThe user's UTC offset in minutes, if set

data fields

FieldDescription
resourceIdThe ID of the resource that triggered the event
resourceTypeThe type of resource (e.g. "Message", "Channel")
resourceThe 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:

FieldUse
conversation_idThe conversation to reply into
idThe message ID (use as reply_to_message_id to thread your reply)
transcript_txtThe text of what was said
creator_idThe user who sent the message (useful for filtering)
attachmentsArray of attached files or links (see below)

Attachment fields

Each object in attachments includes:

FieldDescription
idAttachment ID — use this to request a signed download URL
type"file" for uploaded files, "link" for web URLs
linkFor "link" type: a directly accessible URL. For "file" type: an internal storage path — not directly downloadable
filenameOriginal filename (file attachments only)
mime_typeMIME type (file attachments only)
length_in_bytesFile size in bytes (file attachments only)

File attachments are not pre-signed. To download a file, exchange its id for 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:

How to Handle Incoming Webhook Payloads | Carbon Voice Developer Portal