Stream events (SSE)
Streams the brand’s events as server-sent events, one data: frame per
event, id: carrying the event’s cursor. Reconnect with Last-Event-ID
(or the cursor query parameter) to resume without a gap — the stream
carries the same events the webhooks deliver, byte for byte, which is
why a webhook signature and a stream frame can never disagree.
A frame. id: is the resume cursor and nothing else; event: is
the event’s kind; data: is one JSON object
{"id","type","timestamp","data":{...}} — id there is the event’s own
stable identity (the same value a webhook of it carries, so both sinks
deduplicate against one key), and the inner data addresses what moved:
chat_id plus the seq_from/seq_to range of that chat’s event
sequence. Read message content and history from
GET /v1/chats/{chat}/events using that range. Kinds today are
message.received, message.sent, message.delivered, message.read,
message.failed, chat.read and chat.window.opened; the set grows additively, so
skip a kind you do not handle rather than failing on it, and expect new
members inside data too.
One message can produce several events. An inbound message produces
message.received and, when it opens the customer window,
chat.window.opened. Your reply then produces message.sent and may
later produce message.delivered. Do not treat every event as a reason
to reply; choose the event kinds that are actual triggers for your
integration.
Connecting. Without a cursor the stream starts at the present
moment — it does NOT replay history. During ordinary operation, use the
account event log to recover missed account events and
GET /v1/chats/{chat}/events to read chat content. Account-event history
has no fixed minimum retention period in beta, and an environment reset
may erase it. The server sends a
retry: hint on connect and a : keepalive comment while idle;
neither is an event. There is no scheduled account-event pruning today.
If history is no longer available, a cursor older than its retained
beginning is refused with HTTP 410 cursor_expired rather than answered
with silence. A
cursor beyond the latest committed event is refused with HTTP 422
invalid_paging, while a cursor exactly at the latest event is the
ordinary caught-up position. An empty cursor is refused too: omit it to
start at the present position.
Cursors are scoped to the brand and the test/live partition selected
by the key. Each partition has its own dense sequence from 1, while
that partition is deliberately absent from the wire. If you reconnect
at cursor 42 with a key for the other partition, the server answers 200
and resumes at that partition’s event 42; cursor_expired cannot signal
the swap. Keep separate cursors for sk_test_ and sk_live_ keys.
Webhooks. The same events are delivered as signed webhooks
(Standard Webhooks: webhook-id, webhook-timestamp,
webhook-signature, verifiable with any off-the-shelf library).
Register a destination for them under /v1/webhook_endpoints.
Stream events (SSE)
curl https://whatsapp.messages.api.linqapp.com/v1/streams/events \
-H "Authorization: Bearer $LINQ_WHATSAPP_API_KEY"