Webhooks
The webhook_url field on the Chat endpoint receives the agent result after the original request has ended. It is a per-call callback, suitable when your application should not hold a connection open while the agent runs.
Asynchronous flow
Section titled “Asynchronous flow”- Send
webhook_urland do not usesync: trueinPOST /chat/{id}. - The API records the input and returns
202 Acceptedwithstatus: "processing"orstatus: "debounced". - The agent runs in the background.
- When there is a non-empty external response, SquadOS sends a
POSTto the supplied URL.
curl -X POST https://api.squados.io/v1/chat/API_INBOX_ID \ -H "Authorization: Bearer pk_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "message": "What is the status of order 1234?", "sync": false, "webhook_url": "https://integration.example.com/squados/callback", "metadata": { "correlation_id": "evt_01J7Y8Q3", "ticket_id": "TKT-9981" } }'An input that goes through the grouping queue returns:
{ "status": "debounced", "group_id": "d4e5f6a7-b8c9-0123-def0-234567890123", "conversation_id": "c3d4e5f6-a7b8-9012-cdef-123456789012"}Without grouping, the acknowledgment may contain only:
{ "status": "processing", "conversation_id": "c3d4e5f6-a7b8-9012-cdef-123456789012"}The 202 does not contain the assistant response or its message_id. It confirms that the input was accepted, not that the callback will be delivered.
Response callback
Section titled “Response callback”The callback uses Content-Type: application/json. A common response has this shape:
{ "event": "message.completed", "agent_id": "550e8400-e29b-41d4-a716-446655440000", "success": true, "conversation_id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "message_id": "e5f6a7b8-c9d0-1234-ef01-345678901234", "response": "Order 1234 is being picked and will ship tomorrow.", "model": "provider/model", "credits_used": 3, "attachments_processed": 0, "responding_agent_name": "Support", "metadata": { "correlation_id": "evt_01J7Y8Q3", "ticket_id": "TKT-9981" }, "timestamp": "2026-09-01T14:32:07.000Z"}| Field | Type | Rule |
|---|---|---|
event | string | message.completed for a normal response. |
agent_id | UUID | Agent associated with the execution. |
success | boolean | true on message.completed. |
conversation_id | UUID | Use it to fetch the conversation and reconcile the result. |
message_id | UUID | Identifies the agent message. It may be absent on a path that could not persist an external message. |
response | string | Final text delivered to the external channel. |
model | string | Model actually used; may be absent from follow-up callbacks. |
credits_used | number | Execution credits; may be absent from follow-up callbacks. |
attachments_processed | integer | Number of input attachments; may be absent from follow-up callbacks. |
responding_agent_name | string | Name of the agent that produced this response. |
metadata | object | Correlation data preserved from the input; may be absent when none was supplied. |
timestamp | date-time | Time when the payload was assembled. |
The runtime contains a message.error variant for an internal failure during the delivery stage, with agent_id, conversation_id, error, metadata, and timestamp. It is not a guaranteed receipt for every failure: if the same destination is unreachable, the error payload cannot reach it either. Reconcile missing callbacks through conversation history.
Grouping and metadata
Section titled “Grouping and metadata”With sync: false, nearby messages in the same conversation may be grouped into one execution. Text and attachments are combined chronologically, but the callback preserves only the first available metadata object in the group (first wins).
Use a stable correlation identifier across inputs that may enter the same group. Do not depend on different metadata values from each grouped message.
Transfers and follow-ups
Section titled “Transfers and follow-ups”An agent transfer may produce two message.completed callbacks, in this order:
- the source agent message, with
pre_transfer: true; - the destination agent response, without that marker.
Use message_id as the idempotency key and do not treat conversation_id as a unique callback identifier.
An automated follow-up from the same conversation may also reach the stored URL. It uses message.completed and adds:
| Field | Type | Meaning |
|---|---|---|
followup | boolean | Always true for this variant. |
followup_attempt | integer | Logical follow-up attempt number. It is not an HTTP delivery attempt. |
Allowed destination and authentication
Section titled “Allowed destination and authentication”The destination must be a public HTTP or HTTPS URL with resolvable DNS. SquadOS blocks loopback, private and reserved networks, metadata endpoints, and local or internal hostnames. Validation happens during asynchronous processing, so a blocked URL may still receive an initial 202 while never receiving a callback.
Use HTTPS. The callback sends only the Content-Type: application/json header: it has no signature, Authorization, or custom headers. If the receiver requires authentication, use an opaque high-entropy path and also validate an unpredictable correlation value in metadata. Do not place credentials reused by other systems in this URL.
Delivery and failure recovery
Section titled “Delivery and failure recovery”Respond with any 2xx status. Non-2xx responses, network failures, and blocked URLs are recorded as failures; the receiver’s response body is recorded up to 5,000 characters.
The current callback makes at most one delivery attempt and has no automatic retry. Design the flow as at-most-once:
- persist the payload quickly and respond with
2xx; - deduplicate by
message_id; - do not use callback arrival as the only source of truth;
- if it does not arrive, call
GET /conversations/{conversationId}/messagesto reconcile history.
See Chat for the complete request contract and Conversations for history retrieval.