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:
- Parse
tandsfromLinq-Signature. - Compute
hmac_sha256("{t}.{raw_body}", LINQ_WEBHOOK_SECRET); constant-time compare. - Reject if timestamp skew > 5 min (replay guard).
- Dedup via
Linq-Event-Idseen within 24h. - Log with
X-Request-IDtagged 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?