New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@photon-ai/linq

Package Overview
Dependencies
Maintainers
4
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@photon-ai/linq

Linq adapter for Spectrum.

latest
Source
npmnpm
Version
0.2.0
Version published
Weekly downloads
0
Maintainers
4
Weekly downloads
 
Created
Source

@photon-ai/linq

LinQ adapter for Spectrum.

LinQ is iMessage-native, so most of Spectrum's content model maps directly: text, attachments, voice memos, reactions (tapbacks), replies, rich links, message effects, typing indicators, group rename / icon, and contact cards.

Install

bun add @photon-ai/linq spectrum-ts@^12.7.0

Configure

Provision a bearer token and a webhook signing secret from your LinQ representative, then drop the provider into Spectrum({ providers: [...] }):

import { Spectrum, text } from "spectrum-ts";
import { linq } from "@photon-ai/linq";

const app = await Spectrum({
  projectId: process.env.PROJECT_ID,
  projectSecret: process.env.PROJECT_SECRET,
  providers: [
    linq.config({
      apiKey: process.env.LINQ_API_KEY,
      webhookSigningSecret: process.env.LINQ_WEBHOOK_SECRET,
      defaultFrom: "+12025550123", // optional: number for proactively-created chats
    }),
  ],
});

// Streaming
for await (const [space, message] of app.messages) {
  if (message.content.type === "text") {
    await space.send(text(`echo: ${message.content.text}`));
  }
}

Webhook mode

import {
  FusorJsonWebhookError,
  normalizeFusorWebhookRequest,
} from "@photon-ai/linq";

// Hono / Bun.serve / Next.js / Workers — pass the raw Request.
server.post("/webhooks/fusor", async (c) => {
  try {
    // Spectrum 12 consumes protobuf Fusor envelopes. This also accepts the
    // schema-v1 JSON envelope used by Fusor HTTP subscriptions and verifies
    // its outer signature when a secret is provided.
    const request = await normalizeFusorWebhookRequest(c.req.raw, {
      webhookSecret: process.env.SPECTRUM_WEBHOOK_SECRET,
    });
    return app.webhook(request, async (space, message) => {
      await space.send("got it");
    });
  } catch (error) {
    if (error instanceof FusorJsonWebhookError) {
      return new Response(error.message, { status: error.status });
    }
    throw error;
  }
});

The normalizer leaves protobuf Fusor deliveries and native Spectrum JSON webhooks untouched so app.webhook() can auto-detect them. If you also receive native Spectrum JSON, configure its separate Spectrum({ webhookSecret }).

Webhook signing

Inbound POSTs are verified in two independent layers:

LayerSecretSet onHeader(s)Signed payload
Outer — JSON relay came from FusorwebhookSecretnormalizeFusorWebhookRequest(...)x-spectrum-signature / x-spectrum-timestampv0:{timestamp}: + raw body bytes, signature prefixed v0=
Inner — event came from LinQwebhookSigningSecretlinq.config({...})x-webhook-signature / x-webhook-timestamp{timestamp}.{body}

Both use HMAC-SHA256 and are opt-in: when a secret is omitted, that layer's check is skipped. Set both in production — the outer secret proves the request reached you through the Fusor JSON relay, while the inner one proves the raw payload originated at LinQ. Spectrum's separate native JSON webhook uses Spectrum({ webhookSecret }) instead.

Only the inner LinQ layer is replay-guarded (replayToleranceSec); the outer Fusor layer intentionally does not enforce timestamp freshness, since Fusor retries can legitimately reuse an older sign-time.

Config

FieldRequiredDefaultDescription
apiKeyyes—LinQ bearer token (outbound + media downloads).
webhookSigningSecretrecommended—Per-subscription HMAC secret. When set, inbound webhooks are verified (HMAC-SHA256 over {timestamp}.{body}) and replay-guarded. When omitted, verification is skipped.
defaultFromno—Provisioned phone number used when creating a new chat for a proactive send.
baseUrlnoSDK defaultOverride the LinQ API base URL.
replayToleranceSecno300Reject webhooks whose timestamp is older than this.

Content support

Spectrum contentLinQ
texttext part
attachmentupload → media part (attachment_id)
voicevoice-memo bubble
richlinklink part (sole part)
replythreaded reply_to
effectiMessage effect + inner part
reactiontapback (or custom emoji)
typingtyping indicator
renamegroup name update
avatargroup icon update
contactshared as a .vcf media attachment
groupone message with multiple parts
customescape hatch (raw merged into the message body)
poll / poll_optionunsupported (LinQ has no polls) → UnsupportedError

FAQs

Package last updated on 03 Aug 2026

Related posts