Linq

Bots that text over iMessage and SMS through Linq — DMs and group chats, media both ways, and native tapback reactions, mapping Linq chats to the Chat SDK thread/message/reaction model.

Vendor-official adapter maintained by Linq, not Vercel or Chat SDK contributors. For feature requests, bug reports, and support, file an issue on the adapter's repo.

Install

pnpm add @linqapp/chat-sdk-adapter

Quick start

lib/bot.ts
import { Chat } from "chat";
import { createMemoryState } from "@chat-adapter/state-memory";
import { createLinqAdapter } from "@linqapp/chat-sdk-adapter";

export const bot = new Chat({
  userName: "Linq Bot",
  adapters: {
    linq: createLinqAdapter({
      apiKey: process.env.LINQ_API_KEY,
      signingSecret: process.env.LINQ_WEBHOOK_SECRET,
    }),
  },
  state: createMemoryState(),
});

bot.onDirectMessage(async (thread, message) => {
  await thread.subscribe();
  await thread.post(`You said: ${message.text}`);
});

bot.onReaction(["thumbs_up"], async (event) => {
  await event.thread.post("Appreciate the tapback 🫡");
});

The adapter maps a Linq chat to a Chat SDK thread, a text to a message, and an iMessage tapback to a reaction, so the rest of the Chat SDK API (subscriptions, handlers, posts, reactions) works exactly the same as with any other adapter.

Configuration

Prop

Type

Platform setup

  1. Create a Linq account and copy your API key. For testing, the Linq CLI provisions a sandbox number: linq signup --phone <your cell>.
  2. Create a webhook subscription pointing at the route that forwards to bot.webhooks.linq, and subscribe to:
    • message.received
    • reaction.added
    • reaction.removed
  3. Copy the signing secret returned by the subscription into signingSecret.

Other event types are acknowledged with a 200 and ignored.

Webhook events

EventRole
message.receivedDrives Chat SDK message processing
reaction.addedDrives reaction handlers
reaction.removedDrives reaction handlers
app/api/webhooks/linq/route.ts
import { after } from "next/server";
import { bot } from "@/lib/bot";

export const runtime = "nodejs"; // raw body + crypto; not edge

export async function POST(request: Request) {
  return bot.webhooks.linq(request, {
    waitUntil: (task) => after(() => task),
  });
}

Requests are verified with HMAC-SHA256 and a replay-window check before dispatch; an invalid or stale signature returns 401. Passing waitUntil lets work continue after the response is sent in serverless environments like Vercel.

Thread IDs

Thread IDs are stable and always take the form linq:{chatId}, so a conversation maps to the same Chat SDK thread whether it first arrives via webhook or API.

encodeThreadId

adapter.encodeThreadId(data: { chatId: string; isGroup?: boolean }): string;
const encoded = adapter.encodeThreadId({ chatId: "3caaf1a0-ef9f-46e0-8c22-31e82c8514dc" });
// "linq:3caaf1a0-ef9f-46e0-8c22-31e82c8514dc"

decodeThreadId

adapter.decodeThreadId(threadId: string): { chatId: string; isGroup?: boolean };

Group vs. DM identity is tracked internally from webhook payloads; legacy linq:{chatId}:group / linq:{chatId}:dm IDs still decode.

Message format

Outbound markdown is rendered to plain text — Linq delivers message text as-is (iMessage has no markdown). Inbound text is parsed to the Chat SDK AST, with URLs surfaced as link previews.

Reactions

Standard iMessage tapbacks map to normalized Chat SDK emoji in both directions:

Linq tapbackChat SDK emoji
likethumbs_up
dislikethumbs_down
loveheart
laughlaugh
emphasizeexclamation
questionquestion

Custom emoji reactions pass through the default emoji resolver (e.g. 👍thumbs_up), falling back to the raw emoji for anything unmapped. Sticker reactions have no Chat SDK equivalent and are skipped.

Attachments

Inbound media (images, audio, files) arrives as Chat SDK attachments with downloadable data. Outbound attachments and files are sent as Linq media parts: a public HTTPS URL under 10 MB is sent by reference, while raw bytes, non-HTTPS URLs, and files up to 100 MB are pre-uploaded. Messages can be media-only.

Examples

Feature support

Messaging

FeatureSupported
Post message
Edit messageText, first part
Delete message
File uploadsImages, audio, files
StreamingBuffered
Scheduled messages

Rich content

FeatureSupported
Card format
Buttons
Link buttons
Select menus
Tables
Fields
Images in cards
Modals

Conversations

FeatureSupported
Slash commands
Mentions
Add reactionsTapbacks
Remove reactionsTapbacks
Typing indicatorDMs only
DMs
Ephemeral messages
User lookup
Parent subject
Native client
Custom API endpoint

Message history

FeatureSupported
Fetch messages
Fetch single message
Fetch thread info
Fetch channel messages
List threads
Fetch channel info
Post channel message

On this page