Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 40 additions & 18 deletions .agents/skills/integrating-linq/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: Integrates the Linq Partner API for webhook intake, subscription se
# Integrating Linq

Use this workflow when changing Linq integration or `packages/adapter-linq`. Provider facts below
were reverified on **2026-08-22**; recheck current sources before relying on versioned schemas,
were reverified on **2026-09-07**; recheck current sources before relying on versioned schemas,
operations, event names, limits, or delivery behavior.

## Evidence precedence
Expand All @@ -25,13 +25,14 @@ installed SDK exposes it, and a generated SDK union may lag current provider eve

Start at:

- `https://docs.linqapp.com/llms.txt` — current documentation index;
- `https://docs.linqapp.com/guides/webhooks/` — verification, versioning, and delivery guarantees;
- `https://docs.linqapp.com/guides/webhooks/events/` — envelope and event examples;
- `https://docs.linqapp.com/guides/webhooks/subscriptions/` — subscription lifecycle;
- `https://docs.linqapp.com/guides/integrations/chat-sdk/` — Linq's published Chat SDK integration;
- `https://docs.linqapp.com/guides/messaging/` — message behavior;
- `https://docs.linqapp.com/getting-started/sdks/` — official client behavior; and
- `https://docs.linqapp.com/llms.txt` — channel index; follow the iMessage
`https://docs.linqapp.com/channel/imessage/llms.txt` for current page paths;
- `https://docs.linqapp.com/channel/imessage/guides/webhooks/` — verification, versioning, and delivery guarantees;
- `https://docs.linqapp.com/channel/imessage/guides/webhooks/events/` — envelope and event examples;
- `https://docs.linqapp.com/channel/imessage/guides/webhooks/subscriptions/` — subscription lifecycle;
- `https://docs.linqapp.com/channel/imessage/guides/integrations/chat-sdk/` — Linq's published Chat SDK integration;
- `https://docs.linqapp.com/channel/imessage/guides/messaging/` — message behavior;
- `https://docs.linqapp.com/channel/imessage/getting-started/sdks/` — official client behavior; and
- `https://cdn.linqapp.com/openapi/linq-api-v3.yaml` — canonical endpoints, schemas, and event enum.

Use the index to discover current page paths rather than guessing old documentation URLs.
Expand Down Expand Up @@ -78,13 +79,16 @@ The signed content is:
{webhook-id}.{webhook-timestamp}.{raw_body}
```

The current adapter verifies this scheme directly with `standardwebhooks@1.0.0`. Standard secrets
The current adapter verifies this scheme directly with `standardwebhooks@1.1.1`. Standard secrets
use the `whsec_` format, and signatures use `v1,{base64}` values. Linq's current Chat SDK integration
page still describes the older `{timestamp}.{body}` HMAC shape, while the primary webhook guide
specifies Standard Webhooks over `{webhook-id}.{webhook-timestamp}.{body}`. The documentation also
describes SDK `webhooks.unwrap()`, but installed `@linqapp/sdk@0.44.3` does not expose that method or
wrapper event types at runtime or in declarations. Treat these as provider documentation/SDK
discrepancies and follow the primary webhook guide plus the installed runtime.
specifies Standard Webhooks over `{webhook-id}.{webhook-timestamp}.{body}`. Installed `@linqapp/sdk@0.62.0` now exposes
`webhooks.unwrap()` and generated event types. Its existence does not replace the adapter-owned
verification/error contract. Keep direct Standard Webhooks verification, explicit trusted
forwarding, and lossless future-event handling. Version 1.1.1 rejects empty decoded secrets and
returns undefined for authenticated empty bodies; the adapter explicitly parses after
authentication to preserve `invalid_json`. Refuse lossy UTF-8 round trips before using the
reference verifier so signatures cannot authenticate substituted bytes.

The repository adapter accepts Standard Webhooks only. Deprecated `X-Webhook-*` signature handling
is not part of its public contract. Partial Standard header sets fail, and verified requests
Expand All @@ -108,15 +112,33 @@ identifies the sender, `chat` contains canonical chat facts including `id`, `is_
`owner_handle`, and message fields such as `id`, `parts`, `sent_at`, `delivered_at`, and `read_at`
live directly on `data`.

Installed `@linqapp/sdk@0.44.3` exposes the subscription event-name enum and useful lower-level resource
types, but no exhaustive webhook envelope union or unwrap runtime. The adapter therefore owns a
stable envelope, a checked-in OpenAPI-derived event-name inventory, curated normalized
message/reaction observations, and a lossless raw form for unknown/future events. The drift check
intentionally does not inventory provider-wide operations.
Installed `@linqapp/sdk@0.62.0` supplies useful message/resource and webhook types. The adapter
retains a stable envelope, the canonical OpenAPI-derived 46-event inventory, curated observations,
and a lossless raw form for unknown/future events. The drift check intentionally does not
inventory provider-wide operations.

Inbound group owner mentions use authenticated `mentions[]`: `is_me: true`, exact owner handle,
and valid half-open UTF-16 ranges, without splitting surrogate pairs. Multiple mentions and
formatting are allowed inbound. Only an absent `mentions` field permits the deprecated singular
first-mention fallback; null, empty, or malformed modern values never fall back. Outbound text
parts still use singular `mention` / `mention_range` and the existing send-side exclusions.

`contact_card.received` is a named/raw line-level observation only. Preserve unknown
reaction/sticker values and per-event `zero_retention` in raw facts; do not invent contact,
retention, download, or persistence workflows.

The installed SDK also includes the `app_clip` message part for a standalone Linq checkout URL. It is
iMessage-only and does not downgrade to SMS or RCS. Keep outbound use on the typed native client;
the adapter normalizes an inbound App Clip URL as ordinary text/link while retaining the full raw
part for title, description, and image metadata.

Read `reference/webhooks.md` for the repository-specific ingress and setup checklist.

## Adapter maintenance baseline

Use Chat/shared `4.40.0`, Linq SDK `0.62.0`, and `standardwebhooks@1.1.1`. The tested Chat peer
floor is `4.40.0`. Run `pnpm check:adapter` with pnpm `12.3.4` and Node.js 22.12 or 24.
TypeScript `7.0.2` builds declarations and maps natively; only introduce a TypeScript 6
compatibility alias if a verified compiler-API consumer needs it. Keep installs explicit and
frozen after lockfile preparation, and review lifecycle permissions narrowly. Example/demo
maintenance is separate from adapter work and must not be inferred from the paths above.
26 changes: 19 additions & 7 deletions .agents/skills/integrating-linq/reference/webhooks.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Linq webhook checklist

Provider facts here were reverified on **2026-08-22**. Start with the current
Provider facts here were reverified on **2026-09-07**. Start with the current
[`docs.linqapp.com` index](https://docs.linqapp.com/llms.txt),
[webhook guide](https://docs.linqapp.com/guides/webhooks/), and
[webhook guide](https://docs.linqapp.com/channel/imessage/guides/webhooks/), and
[canonical OpenAPI](https://cdn.linqapp.com/openapi/linq-api-v3.yaml). Then inspect the resolved
`@linqapp/sdk` types and current repository tests. Record disagreements; do not resolve them from
this summary alone.
Expand All @@ -17,10 +17,12 @@ Always preserve the raw request body until authentication succeeds.
- Verify `{webhook-id}.{webhook-timestamp}.{raw_body}` using the subscription secret.
- Standard secrets use `whsec_` plus base64 key material; signatures contain `v1,{base64}` values.
- Reject timestamps outside five minutes and compare signatures in constant time.
- Use the direct `standardwebhooks` reference implementation. The current SDK's `Webhooks` class is
empty even though provider documentation still describes `webhooks.unwrap()`.
- Use `standardwebhooks@1.1.1` directly. SDK `0.62.0` now has `webhooks.unwrap()`, but the
adapter preserves its exact-byte, trusted-forwarding, and error contract independently.
- Reject empty decoded secrets. Parse JSON explicitly after authentication, including an empty
body, and reject lossy UTF-8 decoding before signature verification.

Linq's current [Chat SDK integration page](https://docs.linqapp.com/guides/integrations/chat-sdk/)
Linq's current [Chat SDK integration page](https://docs.linqapp.com/channel/imessage/guides/integrations/chat-sdk/)
also describes an older `{timestamp}.{body}` HMAC shape. The primary webhook guide and Standard
Webhooks headers are authoritative for this adapter:
`{webhook-id}.{webhook-timestamp}.{raw_body}`. Do not copy the integration page's older signature
Expand Down Expand Up @@ -71,8 +73,9 @@ behavior, or provider delivery reliability. Historical real-delivery observation

The checked-in inventory records only the canonical event-name enum supporting the public typed
event contract. Run `pnpm openapi:check` to detect that schema drift. Installed
`@linqapp/sdk@0.44.3` has no unwrap union; do not use generated SDK webhook wrappers as a
closed-world boundary.
`@linqapp/sdk@0.62.0` includes unwrap and event types; generated wrappers are still not a
closed-world boundary. All 46 canonical names, including raw-only `contact_card.received`, are
checked against OpenAPI.

## Repository setup and storage

Expand Down Expand Up @@ -101,3 +104,12 @@ The `2026-02-03` envelope includes `api_version`, `webhook_version`, `event_type
Before changing parsing, compare the official event guide/OpenAPI, installed SDK declarations,
`packages/adapter-linq/src/webhook.ts`, and current fixtures. Preserve provider facts that cannot be
normalized faithfully in the verified raw envelope.

## Mention and raw-fact boundaries

Inbound `mentions[]` is authoritative whenever present. Owner detection requires an exact owner
handle, `is_me: true`, and a valid UTF-16 range. Owner position and inbound formatting do not
exclude a mention. Only omitted modern fields use the deprecated singular fallback. Outbound
mentions stay singular. Contact-card events identify a line rather than a chat and receive only
named/raw dispatch. Unknown sticker/reaction details and `zero_retention` remain lossless;
applications own any contact, retention, or download workflow.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [20, 24]
node-version: ["22.12.0", "24"]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@ identity.
## Development

```bash
pnpm install
pnpm check
pnpm --filter @forma/linq-chat-sdk-adapter... install --frozen-lockfile
pnpm check:adapter
```

`pnpm check` runs the canonical webhook event-name drift check, lint, formatting, tests,
TypeScript contracts, and workspace builds. CI runs the same check on Node.js 20 and 24.
`pnpm check:adapter` runs the canonical webhook event-name drift check, adapter lint, formatting,
tests, TypeScript contracts, and build. CI installs the full workspace and runs `pnpm check` on
Node.js 22.12 and 24, including the example application's typecheck and build.

The adapter currently uses `@linqapp/sdk@0.44.3` for provider operations, Chat SDK `4.38.1`, and
`standardwebhooks` for inbound authentication. Its verified webhook envelope remains adapter-owned
because the installed Linq SDK does not provide the required verification/event contract.
Exact toolchain and dependency versions live in the [root manifest](package.json),
[adapter manifest](packages/adapter-linq/package.json), and [lockfile](pnpm-lock.yaml).
See the adapter [consumer README](packages/adapter-linq/README.md) for supported contracts and
[maintenance guide](packages/adapter-linq/MODERNIZATION.md) for toolchain policy and release checks.

## Maintenance

Expand Down
8 changes: 4 additions & 4 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@chat-adapter/state-pg": "^4.38.1",
"@chat-adapter/telegram": "^4.38.1",
"@chat-adapter/whatsapp": "^4.38.1",
"@chat-adapter/state-pg": "^4.40.0",
"@chat-adapter/telegram": "^4.40.0",
"@chat-adapter/whatsapp": "^4.40.0",
"@forma/linq-chat-sdk-adapter": "workspace:*",
"@linqapp/sdk": "^0.44.3",
"ai": "^6.0.184",
"chat": "^4.38.1",
"chat": "^4.40.0",
"pg": "^8.20.0",
"zod": "^4.4.3"
},
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
"format": "turbo format",
"format:check": "turbo format:check",
"openapi:check": "pnpm --filter @forma/linq-chat-sdk-adapter openapi:check",
"check": "pnpm openapi:check && pnpm lint && pnpm format:check && pnpm test && pnpm typecheck && pnpm build"
"check": "pnpm openapi:check && pnpm lint && pnpm format:check && pnpm test && pnpm typecheck && pnpm build",
"check:adapter": "pnpm --filter @forma/linq-chat-sdk-adapter openapi:check && turbo lint format:check test typecheck build --filter=@forma/linq-chat-sdk-adapter --cache-dir=.turbo/cache --cache=local:rw"
},
"devDependencies": {
"turbo": "^2.5.0",
"typescript": "^5.8.3",
"turbo": "2.10.12",
"typescript": "7.0.2",
"yaml": "^2.9.0"
},
"engines": {
"node": ">=20"
"node": "^22.12.0 || ^24.0.0"
},
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
"packageManager": "pnpm@12.3.4"
}
1 change: 1 addition & 0 deletions packages/adapter-linq/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
coverage/
Loading
Loading