Skip to content

Event payload reference

The event shape to send: required fields, accepted fields, and what happens to the rest.

This is the shape to send when no adapter fits your stack. Both keyed endpoints read it, and a webhook can carry it too.

The event shape

One event is one JSON object. Send one, an array of them, or an object with an events array. Bulk import also reads one record per line.

json

{
    "thread_id": "contact-4812",
    "session_id": "call-99f1",
    "type": "message.sent",
    "ts": 1755820800000,
    "payload": {
        "role": "assistant",
        "text": "Your order ships tomorrow."
    },
    "source": {
        "platform_msg_id": "msg-7731",
        "agent_version": "support-agent-3.2"
    }
}

The fields

thread_idRequired. The conversation this belongs to, as your own system keys it
session_idOptional. One call or one chat inside that conversation
typeRequired. One of message.received, message.sent, tool.called, tool.result, call.started, call.ended, session.note. call.started and call.ended are what make a session a call, so send them only for voice
tsRequired. Epoch milliseconds of when the message was created, never when you sent it. A value near 1.7e9 is seconds, and we refuse it
channelOptional. voice or chat. Say it when your stack knows; we infer from the call brackets when you do not. Declaring chat on a call.started is refused
source.platform_msg_idRequired. Your own id for this one event
source.agent_versionOptional. Which build of the agent produced it
payload.roleuser, assistant or system
payload.textWhat was said
payload.audio_refWhere the recording is, for a voice turn
payload.toolFor a tool event: name, call_id, arguments, result, error

The limits

Timestamp rangeBetween 2015 and 2100
An idA string of at most 512 characters
Events per payload5000
Note: Do not set ts_precision or clock. The adapter fixes both, at milliseconds and a clock of its own, and an event that sets either one is refused.

Chat or call

A callCarries call.started or call.ended. Those two events are what make it one, and call.ended closes it
A chatCarries neither. Send the turns and nothing else; the gap after the last one closes it
Getting it wrongA chat sent with call.started is filed as a call, on every screen that splits the two

The two connector settings

secretOptional. An HMAC-SHA256 over the raw body, for the webhook path. With no secret set, the token in the URL is the only guard
trust_sender_attributionDefault true. Set it false to ignore human-agent and platform-injected claims from the sender

With a secret set, send the digest as hex in an x-signature header. An x-hub-signature-256 header works too, and anything before an equals sign is ignored.

Sender attribution

Every turn your deployment produced is tagged, so a reply a person typed is never treated as the agent. An assistant turn is tagged as the agent, a system turn as platform injected, and a user turn is left alone.

You can say so per event with payload.source_tag, set to agent, platform_injected or human_agent. On a human turn, payload.operator_external_id and payload.operator_display_name name who replied.

Send a work identity there, a staff email or a console user id, never a customer's own details.

Set trust_sender_attribution false where you do not control the sender. A claim that excuses the agent is then dropped and the default stands. A claim that blames the agent is kept either way.

Read next

  • Conversations · List conversations, and open one with its turns, findings and scores.
  • Scores and findings · The number, and which rule broke on which turn. Two endpoints, one reader's job.
  • Metrics · Bucketed readings, so you can chart quality beside your own numbers.
This page as markdown