Create docs/wiki/linq-protocol.md
7bc94171ea45 jacobcole 2026-04-23 1 file
new file mode 100644
index 0000000..5866d9e
@@ -0,0 +1,100 @@
+---
+visibility: public
+---
+
+# Linq protocol (as observed)
+
+Based on **linq-sim** (`~/code/cortex/cloudcli/dev-tools/linq-sim/`) and Cortex's `backend/src/routes/sms.ts` normalization layer. This is our ground truth until Jacob gets real Linq partner docs.
+
+## Inbound webhook
+
+`POST $OUR_BASE/api/linq/inbound`
+
+### Headers
+- `Content-Type: application/json`
+- `Linq-Signature: t=<unix_ts>,s=<hex_hmac_sha256>`
+- `Linq-Event-Id: <uuid>` (dedup key)
+
+### Body shape (union of all events)
+
+```json
+{
+ "type": "message.received",
+ "id": "evt_...",
+ "timestamp": 1714065000,
+ "chat_id": "chat_abc",
+ "data": {
+ "message_id": "msg_...",
+ "sender": { "phone": "+15551234567", "name": "..." },
+ "text": "...",
+ "reply_to_message_id": "msg_parent", // NEW — requires S2 sim PR
+ "attachments": []
+ }
+}
+```
+
+### Supported event types
+
+```
+message.received
+message.delivered
+message.read
+message.edited
+message.failed
+reaction.added
+reaction.removed
+chat.typing_indicator.started
+chat.typing_indicator.stopped
+chat.created
+chat.updated
+chat.group_name_updated
+participant.added
+participant.removed
+```
+
+## HMAC verification
+
+```
+signed_payload = f"{timestamp}.{raw_body_bytes}"
+expected_sig = hmac_sha256(signed_payload, secret)
+```
+
+Constant-time compare `expected_sig` vs `s=` value.
+
+Reject if:
+- Signature header missing / malformed → 401
+- `expected_sig` mismatch → 401
+- `now - timestamp > 300` → 401 (skew)
+- `Linq-Event-Id` seen in last 24h → 200 (dedup no-op)
+
+## Outbound partner API
+
+`$LINQ_BASE_URL/api/partner/v3/...`
+
+- `POST /sendMessage` — `{ chat_id, text, reply_to_message_id?, attachments? }`
+- `POST /createChat` — `{ participants: [phone, ...] }`
+- `GET /getChat?chat_id=...`
+- `POST /addParticipant` / `removeParticipant` / `updateChat`
+
+Auth: `Authorization: Bearer $LINQ_API_KEY` (assumed — confirm with real Linq docs).
+
+## Reactions
+
+Set: `{heart, thumbs_up, thumbs_down, laugh, exclaim, question}` (iMessage's native tapbacks). `target_message_id` required on `reaction.added`. linq-sim enforces 400 when missing.
+
+## Threads / replies
+
+iMessage has inline replies (`reply_to_message_id`). linq-sim does NOT currently implement this; [S2](../plans/2026-04-23-initial-roadmap.md#s2-linq-sim-thread-support) adds it. picortex requires it before S7 mobile UI can render reply pills properly.
+
+## Things we don't know yet
+
+- Rate limits on outbound `sendMessage`
+- Attachment size caps
+- Whether Linq preserves read receipts across edits
+- Whether `chat.created` fires for a 1:1 on first message or only on explicit creation
+
+## Related
+
+- [Spec 006 — Linq integration](../specs/006-linq-integration.md)
+- [ADR-0004 — Linq as primary channel](../adrs/0004-linq-primary-channel.md)
+- Cortex: `~/code/cortex/docs/future-plans/2026-03-31-linq-channel-plugin.md`
\ No newline at end of file