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 after the last position you
processed. On read-back surfaces — the transcript, event log, event stream
and replays — zero-day messages carry empty parts and
zero_retention: true. Webhook deliveries and the send response,
including an idempotent retry, carry the content. When ephemeral messages
are enabled, expired messages and their related events are omitted, so the
stream’s id: values can have retention gaps.
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 published minimum retention period yet. 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 (a 2xxx
error.code, 2032) rather than answered with silence. A
cursor beyond the latest committed event is refused with HTTP 422
invalid_paging (a 1xxx error.code, 1016), 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.
A key that acts in an environment cannot open this stream. It is
refused with HTTP 403 environment_stream_unavailable (a 2xxx
error.code, 2055) before anything is read; an environment’s events
reach you through the webhook endpoints registered under its key. A
key that acts in no environment — every key issued before environments
existed — streams the events of numbers in none. Events of numbers in
an environment share this stream’s cursor sequence but are not
delivered on it, so the id: values you see may skip; resume from the
last id: you processed, and nothing you are shown is lost.
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"