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 only secret_suffix

Events

Only enabled endpoints subscribed to the event receive the delivery.

EventWhen it fires
message.receivedAn inbound SMS is persisted
message.deliveredAn outbound SMS is marked delivered
message.failedAn outbound SMS fails or is rejected at send time
call.completedA call reaches a terminal status
call.loggedA call is logged with a disposition
call.summarizedConsio finishes generating the summary of a call
order.attributedAn 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.

EventPayload object
message.received, message.delivered, message.failedmessage
call.completed, call.loggedcall
call.summarizedcall_summary
order.attributedorder_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:

  • POST to your URL
  • Content-Type: application/json
  • User-Agent: Consio-Webhooks/1.0
  • X-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).

  1. Read the raw request body before JSON parsing
  2. Parse t and v1 from X-Consio-Signature
  3. Reject if |now - t| > 300 seconds (replay protection)
  4. 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.

Did this page help you?