Spec 006 — Linq integration

Status: Draft Related: PRD FR-1..FR-4, ADR-0004, Wiki: linq-protocol

Goal

Speak fluent Linq, both inbound (webhook ingest) and outbound (partner API), with HMAC signing parity. Testable end-to-end against linq-sim without needing real phone numbers.

Inbound

POST /api/linq/inbound

Headers:

  • Linq-Signature: t=<unix_ts>,s=<hex_hmac_sha256> (Cortex's exact shape)
  • Linq-Event-Id: <uuid>
  • Content-Type: application/json

Verification:

  1. Parse t and s from Linq-Signature.
  2. Compute hmac_sha256("{t}.{raw_body}", LINQ_WEBHOOK_SECRET); constant-time compare.
  3. Reject if timestamp skew > 5 min (replay guard).
  4. Dedup via Linq-Event-Id seen within 24h.
  5. Log with X-Request-ID tagged to event id.

Supported event types (from linq-sim):

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

New for v1: message.received with data.reply_to_message_id set (requires S2 linq-sim PR).

Dispatch table: each event → a handler in src/channels/linq/handlers/*.ts. Handlers are pure wrt Linq — they call into the core chat service.

Outbound

Single client at src/channels/linq/client.ts:

class LinqClient {
  sendMessage({ chatId, text, replyToMessageId?, attachments? })
  createChat({ participants })
  getChat({ chatId })
  addParticipant({ chatId, participant })
  removeParticipant({ chatId, participant })
  updateChat({ chatId, patch })
}

Base URL from LINQ_BASE_URL. For dev, points at linq-sim (http://127.0.0.1:8447). Calls to sim still succeed but are captured for inspection in the sim UI.

Retry: exponential backoff up to 3 attempts on 5xx. 4xx never retried.

Channel abstraction

interface Channel {
  name: string
  verifyInbound(req): Promise<ParsedEvent>
  send(msg: OutboundMessage): Promise<{id: string}>
  supports(feature: "reactions" | "threads" | "typing"): boolean
}

LinqChannel implements this. Future OpenChatChannel (Wiki: openchat-adapter) will too.

Testing

  • Unit: HMAC signer/verifier, timestamp skew, signature format parsing.
  • Integration: linq-sim roundtrip — send via sim's admin UI, picortex handles, responds via sim-captured /api/partner/v3/sendMessage.
  • E2E: full conversation against linq-sim.

Open questions

  • OQ1: Do we store inbound raw event JSON or normalized? (Store raw + normalized both — Cortex does this.)
  • OQ2: What attachment types must we support at v0.1? (Text-only is fine; images/voice memos are v0.2.)
  • OQ3: Linq's rate limits?
[[curator]]
I'm the Curator. I can help you navigate, organize, and curate this wiki. What would you like to do?