Event catalog
Section: DOC-IN-webhooks-events#event-catalog.
This reference defines outgoing webhook payloads, delivery headers and signature verification. Each event below has a dedicated schema and example page. Start with Receive a completed order summary to build the customer workflow, or Recover a missing backend update to investigate a delivery. Conversation streaming has a separate contract.
Webhook events
Section: DOC-IN-webhooks-events#webhook-events.
Webhook events are delivered as HTTP POST requests to the endpoints and subscriptions you manage via the Webhook APIs.
Delivery envelope
Section: DOC-IN-webhooks-events#delivery-envelope.
The request body is the event payload itself — there is no wrapper object. The body's
top-level keys are the payload fields listed under Payload shapes
(event_context plus the event's own fields), encoded as JSON with snake_case names — this
differs from the camelCase used by API responses.
The table and example below describe the documented timestamped-signature format. Confirm the actual header and signing configuration for your deployed endpoint before relying on it. The signed body contains the authoritative event_context.event_name and event_context.event_id; separate routing and deduplication headers are not covered by the documented body HMAC:
| Header | Description |
|---|---|
X-Convoy-Event-Type | The event type string, e.g. llm.message_published |
X-Convoy-Idempotency-Key | {event_id}:{endpoint_id} — check against signed body identity and your configured endpoint before use |
X-Convoy-Signature | t={unix_seconds},v1={hex} — see the verification procedure below |
An illustrative llm.message_published delivery in this format looks like this:
POST /your-endpoint HTTP/1.1
Content-Type: application/json
X-Convoy-Event-Type: llm.message_published
X-Convoy-Idempotency-Key: d3b842c8-b19a-4205-a06f-c57cbf313582:01M0FJHPDVYYBA1V856RK6K91P
X-Convoy-Signature: t=1787229727,v1=050d3756a2953051bfb045490398894546c0d1a7c6fdf3c09d206fdeeeb92255
{
"event_context": { "event_name": "llm.message_published", "event_id": "d3b842c8-b19a-4205-a06f-c57cbf313582", "...": "..." },
"message": { "...": "..." }
}
After verifying the signature, route by signed event_context.event_name and
validate the expected schema and scope. Reject disagreement with
X-Convoy-Event-Type. Derive deduplication from signed event_context.event_id,
your configured endpoint and payload digest; reject a conflicting idempotency
header. A valid body signature does not authenticate arbitrary header replacements.
Verify the signature before processing the payload. The signing secret is the value returned
in the secret field when you call list-endpoints for the receiving endpoint.
In this format, t is the signing time in Unix seconds. v1 is the hex-encoded HMAC-SHA256 of the
string {t},{raw_body} — the timestamp, a comma, then the raw request body — computed with
the per-endpoint secret. Two details matter and are easy to get wrong: the signed input is
not the body alone, and the digest is hex, not base64.
Verify it before processing the payload. A missing or invalid signature cannot establish the sender's authenticity; reject the request and inspect the endpoint's configured format and secret through an authorized channel.
Use a verifier compatible with the signature format above, or implement all of these checks:
- Bound header/body size and the number of signature fields. Parse safely; missing separators, invalid integers, repeated timestamp fields and malformed hex must reject without raising an unhandled error.
- Require one signing timestamp and at least one supported version signature. Preserve multiple
v1values during key rotation instead of overwriting them in a dictionary. - Enforce a bounded timestamp tolerance (five minutes is an example receiver setting), with an explicit clock-skew policy.
- Compute HMAC-SHA256 over the original timestamp text, a comma and the raw body bytes. Compare equal-length decoded digests in constant time against the small configured set of current/overlap secrets. Never log those secrets or the signature.
- After verification, enforce the expected scope and event schema. Derive type and event identity from signed
event_context.event_nameandevent_context.event_id, bound to your configured endpoint. Reject conflicting type/idempotency headers: those separate headers are not covered by the body HMAC. Atomically persist the scoped event identity, payload digest and pending work before acknowledging. The timestamp tolerance limits old replays; it does not prevent duplicates within the window.
Pass the raw request body bytes, exactly as received. Re-serializing the parsed JSON changes the bytes (key order, whitespace) and the signature will not match.
Five minutes is an example receiver tolerance, not a platform-imposed acceptance window. Choose and enforce a bounded policy appropriate to the receiver’s clock skew.
Secret rotation: the current public update-endpoint operation changes the endpoint's name, URL and description. It does not accept a secret field, and there is no public secret-rotation operation. Do not use an endpoint-metadata update as evidence that a signing key changed. Qualify any separately supported rotation procedure for your deployment, including receiver overlap and signatures on new and retried deliveries, before relying on it.
Event types
Section: DOC-IN-webhooks-events#event-types.
| Event | Owner | Direction | Emitted when |
|---|---|---|---|
llm.message_published | Conversations | Travila → your endpoint (outgoing) | An assistant/tool message is produced or an assistant message snapshot is updated. |
llm.generation_started | Conversations | Travila → your endpoint (outgoing) | A generation run starts for a conversation. |
llm.generation_completed | Conversations | Travila → your endpoint (outgoing) | A generation run reaches a terminal outcome. Inspect status and correlate run_id. |
llm.tool_call_started | Delegation and approvals | Travila → your endpoint (outgoing) | A tool call is dispatched for platform or client execution. |
llm.tool_call_approval_required | Delegation and approvals | Travila → your endpoint (outgoing) | A routed tool call is parked for human approval with a decision deadline. |
llm.tool_call_completed | Delegation and approvals | Travila → your endpoint (outgoing) | A tool call reaches a terminal outcome, including a rejected or timed-out approval. |
Subscribe only to what you act on. llm.message_published and llm.generation_completed
can both concern a normal assistant turn. Give them distinct lifecycle handlers and correlate their identities so they do not trigger the same business action twice.
Payload shapes
Section: DOC-IN-webhooks-events#payload-shapes.
Payload field names use snake_case.
llm.message_published
Section: DOC-IN-webhooks-events#llmmessage_published.
See llm.message_published payload, example and producing operations.
Webhook JSON uses protobuf field names (snake_case).
| Field | Type | Presence | Meaning |
|---|---|---|---|
event_context | EventContext | Optional | Event provenance and routing metadata associated with the emitted event. |
message | Message | Optional | The generated assistant/tool message |
Full payload: LLMMessagePublishedEvent.
llm.generation_started
Section: DOC-IN-webhooks-events#llmgeneration_started.
See llm.generation_started payload, example and producing operations.
Webhook JSON uses protobuf field names (snake_case).
| Field | Type | Presence | Meaning |
|---|---|---|---|
event_context | EventContext | Optional | Event provenance and routing metadata associated with the emitted event. |
run_id | string | Optional | Unique identifier for the workflow run |
config | GenerationConfig | Optional | Optional snapshot of generation configuration used for this run |
starting_sequence | string (int64) | Optional | Conversation sequence number at the moment the run started (if known) Pattern: ^-?\d+$. |
Full payload: LLMGenerationStartedEvent.
llm.generation_completed
Section: DOC-IN-webhooks-events#llmgeneration_completed.
See llm.generation_completed payload, example and producing operations.
Webhook JSON uses protobuf field names (snake_case).
| Field | Type | Presence | Meaning |
|---|---|---|---|
event_context | EventContext | Optional | Event provenance and routing metadata associated with the emitted event. |
run_id | string | Optional | Unique identifier for the workflow run |
status | string (WORKFLOW_STATUS_RUNNING, WORKFLOW_STATUS_COMPLETED, WORKFLOW_STATUS_FAILED, WORKFLOW_STATUS_TIMED_OUT, WORKFLOW_STATUS_CANCELED) or integer (int32) | Optional | Terminal status of the run |
loop_count | integer (int32) | Optional | Number of agent loops/steps executed Minimum: -2147483648. Maximum: 2147483647. |
duration_ms | string (int64) | Optional | End-to-end run duration in milliseconds Pattern: ^-?\d+$. |
usage | Usage | Optional | Optional usage accounting for the run |
error | RpcError | Optional | Structured error for FAILED/TIMED_OUT/CANCELED runs. |
usage_by_model | Array of UsageByModel | Optional | Per-model breakdown of the same usage usage aggregates. Additive: usage stays the run-level total so existing consumers are unaffected, while billing keys its charges on model. Empty when the producer predates it. |
turn_key | string | Optional | Identifies the conversation turn this run served: the message_id of the opening user message, resolved once at run start and stamped on every message the run emits as Message.source_user_message_id. Empty when the producer predates this field, or when the turn is unresolvable — never guess one, an empty key means "no turn attribution". |
Values of status
Lifecycle state of a workflow execution, distinct from the reason a terminal execution ended.
| Value | No. | Form | Meaning |
|---|---|---|---|
WORKFLOW_STATUS_RUNNING | 1 | Canonical | The workflow has started and has not reached a terminal outcome. |
WORKFLOW_STATUS_COMPLETED | 2 | Canonical | The workflow finished successfully. |
WORKFLOW_STATUS_FAILED | 3 | Canonical | The workflow ended because execution failed; inspect its error or end reason. |
WORKFLOW_STATUS_TIMED_OUT | 4 | Canonical | The workflow ended because its execution time limit elapsed. |
WORKFLOW_STATUS_CANCELED | 5 | Canonical | The workflow was cancelled before normal completion. This enum retains its existing CANCELED wire spelling. |
Full payload: LLMGenerationCompletedEvent.
llm.tool_call_started
Section: DOC-IN-webhooks-events#llmtool_call_started.
See llm.tool_call_started payload, example and producing operations.
Fires when a tool call is dispatched — both for tools the platform runs itself and for client-side tools, which are dispatched to you.
Webhook JSON uses protobuf field names (snake_case).
| Field | Type | Presence | Meaning |
|---|---|---|---|
event_context | EventContext | Optional | Event provenance and routing metadata associated with the emitted event. |
tool_call | ToolCall | Optional | Tool-call snapshot before a result is available, including its identity, name, server and arguments. For llm.tool_call_started the status is EXECUTING; for llm.tool_call_approval_required it is REQUIRES_CONFIRMATION and requires_approval_at is the decision deadline. result_json is unset. |
Full payload: ToolCallStartedEvent.
tool_call.status is always TOOL_EXECUTION_STATUS_EXECUTING here: the event says what was
dispatched, not what came back.
For a client-side tool the run is parked on you from this moment, exactly as
llm.tool_call_approval_required parks it on an approver. The payload does not carry a flag
distinguishing the two kinds, so match on the tool names you declared in clientTools — those
are the only ones you are expected to run. It also does not carry the answer deadline; read
clientToolDeadlineAt from
list-pending-client-tools when you need it.
llm.tool_call_completed
Section: DOC-IN-webhooks-events#llmtool_call_completed.
See llm.tool_call_completed payload, example and producing operations.
Fires when a tool call reaches a terminal state — a result you submitted, a result the platform produced, or a timeout.
Webhook JSON uses protobuf field names (snake_case).
| Field | Type | Presence | Meaning |
|---|---|---|---|
event_context | EventContext | Optional | Event provenance and routing metadata associated with the emitted event. |
tool_call | ToolCall | Optional | Final tool call: status is terminal (SUCCESS|FAILED|CANCELLED|TIMED_OUT), result_json populated. |
execution_time_ms | integer (int32) | Optional | Wall-clock execution time in milliseconds (0 when unknown, e.g. dispatch error). Minimum: -2147483648. Maximum: 2147483647. |
Full payload: ToolCallCompletedEvent.
llm.tool_call_approval_required
Section: DOC-IN-webhooks-events#llmtool_call_approval_required.
See llm.tool_call_approval_required payload, example and producing operations.
A routed tool call is waiting for human approval. This event uses the same native payload
shape as llm.tool_call_started; the signed event name and tool-call status distinguish them.
The call has TOOL_EXECUTION_STATUS_REQUIRES_CONFIRMATION and a requires_approval_at decision deadline.
Webhook JSON uses protobuf field names (snake_case).
| Field | Type | Presence | Meaning |
|---|---|---|---|
event_context | EventContext | Optional | Event provenance and routing metadata associated with the emitted event. |
tool_call | ToolCall | Optional | Tool-call snapshot before a result is available, including its identity, name, server and arguments. For llm.tool_call_started the status is EXECUTING; for llm.tool_call_approval_required it is REQUIRES_CONFIRMATION and requires_approval_at is the decision deadline. result_json is unset. |
Full payload: ToolCallStartedEvent.
Use list-pending-approvals to reconcile calls
awaiting a decision, and submit-tool-approvals
to resolve them. A pending call waits for a decision or its deadline; other calls
may proceed under the MIXED policy. Treat a missing deadline as unknown and decide
promptly, rather than assuming the call can wait indefinitely.
Conversation updates
Section: DOC-IN-webhooks-events#conversation-updates.
This catalog describes outgoing webhooks; it does not define an incoming-webhook endpoint. Scheduled callbacks send the payload configured on the schedule and use a separate signed-token protocol. They do not share these event payload schemas.
Conversation updates have separate delivery and recovery rules from webhooks. See streaming availability, event interpretation and reconnection requirements. Never substitute a conversation activity sequence for a message sequence or treat an idle conversation as proof that a particular run completed.
Document ID: DOC-IN-webhooks-events. Section identities and revisions.