Webhooks
Consio can POST JSON events to your HTTPS endpoint when SMS messages are received, delivered, or fail. You manage endpoints over the Public API; deliveries are signed so you can verify authenticity.
Create body
{
"url": "https://example.com/webhooks/consio",
"description": "Production SMS events",
"events": ["message.received", "message.delivered", "message.failed"]
}Constraints:
- URL must be absolute HTTPS
- Max 20 endpoints per workspace
- Create response includes
secret(whsec_…) once; later responses expose onlysecret_suffix
Events
Only enabled endpoints subscribed to the event receive the delivery.
| Event | When it fires |
|---|---|
message.received | An inbound SMS is persisted |
message.delivered | An outbound SMS is marked delivered |
message.failed | An outbound SMS fails or is rejected at send time |
call.completed | A call reaches a terminal status |
call.logged | A call is logged with a disposition |
call.summarized | Consio finishes generating the summary of a call |
order.attributed | An order is attributed to a call or an SMS |
Payload
Every delivery carries the same envelope — event_id, event, workspace_rid — plus one object named after the resource the event describes.
Identifiers are opaque resource IDs (rid), never numeric database IDs.
| Event | Payload object |
|---|---|
message.received, message.delivered, message.failed | message |
call.completed, call.logged | call |
call.summarized | call_summary |
order.attributed | order_attribution |
Message events
{
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"event": "message.received",
"workspace_rid": "…",
"message": {
"rid": "…",
"type": "SMS",
"content": "Hello",
"trigger_type": "INBOUND",
"customer_rid": "…",
"phone_number_rid": "…",
"user_rid": null,
"ai_agent_rid": null,
"customer_phone_number": "+15551234567",
"external_status": null,
"sent_at": null,
"delivered_at": null,
"failed_at": null,
"created_at": "2026-07-24T12:00:00Z",
"updated_at": "2026-07-24T12:00:00Z"
}
}Call events
call.completed and call.logged carry the same call object.
{
"event_id": "6f1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
"event": "call.completed",
"workspace_rid": "…",
"call": {
"rid": "…",
"status": "completed",
"trigger_type": "inbound",
"customer_rid": "…",
"phone_number_rid": "…",
"user_rid": null,
"ai_agent_rid": "…",
"campaign_rid": null,
"customer_phone_number": "+15551234567",
"duration": 128,
"disposition": null,
"notes": null,
"logged_at": null,
"created_at": "2026-07-24T12:00:00Z",
"updated_at": "2026-07-24T12:02:08Z"
}
}On call.logged, disposition and logged_at are set — logged_at is when the disposition was submitted:
{
"event_id": "9c8b7a65-4321-4fed-9876-0123456789ab",
"event": "call.logged",
"workspace_rid": "…",
"call": {
"rid": "…",
"status": "completed",
"trigger_type": "outbound_click",
"customer_rid": "…",
"phone_number_rid": "…",
"user_rid": "…",
"ai_agent_rid": null,
"campaign_rid": null,
"customer_phone_number": "+15551234567",
"duration": 128,
"disposition": "closed_sale",
"notes": "Customer confirmed the reorder",
"logged_at": "2026-07-24T12:04:11Z",
"created_at": "2026-07-24T12:00:00Z",
"updated_at": "2026-07-24T12:04:11Z"
}
}Call summary events
{
"event_id": "1b2c3d4e-5f60-4718-9a2b-3c4d5e6f7081",
"event": "call.summarized",
"workspace_rid": "…",
"call_summary": {
"rid": "…",
"call_rid": "…",
"summary": ["Customer asked about a delayed order"],
"next_steps": ["Send the tracking link"],
"generated_at": "2026-07-24T12:05:00Z"
}
}Order attribution events
{
"event_id": "2d3e4f50-6172-4839-a4b5-c6d7e8f90a1b",
"event": "order.attributed",
"workspace_rid": "…",
"order_attribution": {
"rid": "…",
"order_rid": "…",
"call_rid": "…",
"message_rid": null,
"order_total_price": 149.9,
"call_in_progress_at": "2026-07-24T12:00:00Z",
"order_created_at": "2026-07-24T12:30:00Z",
"cancelled_at": null,
"financial_status": "paid",
"created_at": "2026-07-24T12:30:05Z",
"updated_at": "2026-07-24T12:30:05Z"
}
}At least one of call_rid and message_rid is set: it points to the interaction the order is attributed to. call_in_progress_at is when that interaction happened, for a call as well as for an SMS.
event_id is a stable unique identifier. Retries reuse the same event_id — use it for deduplication.
Delivery is at-least-once. There is no ordering guarantee across endpoints or events.
Request format
Each delivery is:
POSTto your URLContent-Type: application/jsonUser-Agent: Consio-Webhooks/1.0X-Consio-Signature: t=<unix_seconds>,v1=<hmac_sha256_hex>- No redirects followed
- 5 second timeout
Verifying signatures
Signed bytes: "{t}." + raw_body (HMAC-SHA256 with your whsec_… secret).
- Read the raw request body before JSON parsing
- Parse
tandv1fromX-Consio-Signature - Reject if
|now - t| > 300seconds (replay protection) - Recompute the HMAC and compare with a constant-time equality check
def verify_signature(*, secret: str, body: bytes, header_value: str, tolerance_seconds: int = 300) -> bool:
"""Return whether ``header_value`` is a valid signature for ``body``.
Rejects signatures whose timestamp is older or newer than
``tolerance_seconds`` relative to now (replay protection).
Args:
secret: str
Endpoint signing secret (plaintext).
body: bytes
Raw HTTP request body that was signed.
header_value: str
Value of ``X-Consio-Signature``.
tolerance_seconds: int
Max absolute age of the signature timestamp. Default 300s.Updated 2 days ago
