Qubit API v1

Webhooks

Realtime updates: signed deliveries, verified in your language, retried for a day.

A webhook is how the platform tells your system what happened the moment it happens - a customer's reply, a delivery receipt, a conversation handed to an agent, a lead moving stage - without you polling. Register an HTTPS endpoint in the developer portal, choose the events, and every one arrives as a signed POST within seconds, retried for a day if your endpoint is down.

Events

EventWhendata carries
message.receivedA customer sent a message on one of your allow-listed channels.message
message.queuedA message you (or an agent) sent was accepted and queued for delivery.message
message.sentThe channel provider accepted the message.message
message.deliveredThe provider confirmed delivery to the customer's device.message
message.readThe customer read the message (where the channel reports it).message
message.failedThe provider could not deliver the message; `error` says why.message
conversation.createdA new conversation was opened with a contact on an allow-listed channel.conversation
conversation.assignedA conversation was assigned to an agent or a team, or unassigned.conversation, previous_user_id, previous_team_id
conversation.status_changedA conversation was resolved, closed, reopened or marked pending.conversation, previous_status
contact.createdA contact was created - by an inbound message, an agent, an import or the API.contact
contact.updatedA contact's details, identities or tags changed.contact
lead.createdA lead was created for a contact.lead
lead.stage_changedA lead moved to another pipeline stage.lead, from_stage, to_stage
bot.startedA chatbot started talking to a customer on a conversation.conversation, bot
bot.handoffA chatbot handed a conversation to a person; `bot.answers` is what it learned.conversation, bot
bot.completedA chatbot session ended without a handoff - finished, timed out, stopped by an agent or failed; `bot.outcome` says which.conversation, bot
webhook.testA test delivery sent from the developer portal.test

Every message event about something you sent carries the external_reference you gave it, so the delivery of invoice INV-10025 reaches you as an event that says INV-10025 - no lookup needed.

The delivery

POST https://your-system.example.com/hooks/messaging
Content-Type: application/json
User-Agent: CommunicationPlatform-Webhooks/1.0
X-Webhook-ID: 6d2c0a84-1f2e-4b3c-9d4e-5f6a7b8c9d0e       the event id; the same on every retry
X-Webhook-Timestamp: 1789456931                          unix seconds this attempt was signed
X-Webhook-Signature: sha256=4f1c2a…e9b0                  see below
X-Event-ID / X-Event-Type / X-Event-Timestamp            the same values under their original names
X-Delivery-ID / X-Delivery-Attempt                       this attempt, 1-based

{
  "id": "6d2c0a84-1f2e-4b3c-9d4e-5f6a7b8c9d0e",
  "type": "message.delivered",
  "created_at": "2026-09-17T09:13:02+00:00",
  "external_reference": "ORDER-10025",
  "data": {
    "message": {
      "id": "3a1d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
      "external_reference": "ORDER-10025",
      "conversation_id": "9c0e1f2a-3b4c-4d5e-8f60-718293a4b5c6",
      "contact_id": "b7f2c3a4-5d6e-4f70-8a91-b2c3d4e5f607",
      "connection_id": "e51a2b3c-4d5e-4f60-8172-8394a5b6c7d8",
      "direction": "outbound",
      "type": "template",
      "status": "delivered",
      "body": "Hi Rahul, your order 10025 is confirmed.",
      "attachments": [],
      "error": null,
      "sent_at": "2026-09-17T09:13:00+00:00",
      "delivered_at": "2026-09-17T09:13:02+00:00",
      "read_at": null
    }
  }
}

data carries the resource the event is about under its own key (message, conversation, contact, lead) in exactly the shape the reference documents, plus the event's extras - previous_status on a status change, from_stage and to_stage on a stage move. The reference lists every event with its full payload under Webhooks.

Answer any 2xx within 10 seconds and the delivery is done. Do the real work after you have answered - queue it - so a slow database on your side never turns into a retry storm on ours.

Verifying the signature

Every delivery is signed with your endpoint's secret (whsec_…, shown once when the endpoint is created and again only when rotated). The signature is an HMAC-SHA256 over the timestamp and the raw body, joined by a dot:

X-Webhook-Signature: sha256=hex( HMAC-SHA256( secret, timestamp + "." + raw_body ) )

Verify it before you trust anything in the body, and reject a timestamp more than five minutes old - the timestamp is inside the signed string, so a captured delivery cannot be replayed later with a fresh-looking header. Compare in constant time.

Sign the bytes you received, not a re-encoded version of them. Parsing the JSON and serialising it again changes key order or whitespace and the signature will not match.

Verify

The developer portal can send a webhook.test event on demand so you can prove the check end to end before a real one arrives.

Retries and ordering

A non-2xx answer, a timeout (10 seconds) or a connection failure is retried on a schedule: 1 minute, 5 minutes, 15 minutes, 1 hour, 6 hours, 24 hours, then the delivery is marked failed and stays replayable from the portal. Twenty-five consecutive failures disable the endpoint and the workspace's administrators are told.

Two consequences for your handler:

The portal

Settings → Developer → your application → Webhooks shows every endpoint, its secret's age, and every delivery with the payload sent, the response received and a Replay button. Request logs sit beside it: every call your application made, its status and its request id.

Base URL https://communication-api.artofluminaire.com Every response carries X-Request-ID; quote it when you write to support.