Agent skill
Linq Webhooks Skill
Receive and verify Linq webhooks (iMessage, RCS and SMS partner API, signed with Standard Webhooks). Use when setting up Linq webhook handlers, debugging webhook-signature verification, or handling events like message.received, message.sent, message.delivered, reaction.added, and phone_number.status_updated.
Install this skill
npx skills add hookdeck/webhook-skills --skill linq-webhooks
Linq is a partner API for iMessage, RCS and SMS lines. You create webhook subscriptions through the API, and Linq delivers each event as a JSON POST signed with Standard Webhooks headers: webhook-id, webhook-timestamp and webhook-signature. The event name is the top-level event_type field, and event_id is the deduplication key.
When to Use This Skill
- How do I receive Linq webhooks?
- How do I verify Linq webhook signatures (
webhook-signature,whsec_secret)? - How do I handle
message.received,message.deliveredormessage.failedevents? - How do I subscribe to
reaction.addedorphone_?number.status_ updated - Why is my Linq webhook signature verification failing, or rejecting Hookdeck retries?
Verification (core)
Signed content is {webhook-id}.{webhook-timestamp}.{raw body}. Linq signs it with HMAC-SHA256 using the base64-decoded secret (the part after whsec_), base64-encodes the result, and sends one or more space-separated v1,<signature> entries. Use a Standard Webhooks library (standardwebhooks on npm and PyPI), which compares in constant time. Read the webhook-* headers, and when webhook-id is absent fall back to the svix-* headers (svix-id, svix-timestamp, svix-signature), as Hookdeck does.
const { Webhook } = require('standardwebhooks');
const prefix = req.get('webhook-id') ? 'webhook' : 'svix';
const headers = {
'webhook-id': req.get(`${prefix}-id`),
'webhook-timestamp': req.get(`${prefix}-timestamp`),
'webhook-signature': req.get(`${prefix}-signature`),
};
// rawBody: the raw Buffer/string, never re-serialized JSON
const event = new Webhook(process.env.LINQ_WEBHOOK_SECRET).verify(rawBody, headers);
// throws on a bad signature or a timestamp outside the window; returns the parsed body
from standardwebhooks.webhooks import Webhook, WebhookVerificationError
wh = Webhook(os.environ["LINQ_WEBHOOK_SECRET"]) # "whsec_..."
event = wh.verify(raw_body, {"webhook-id": id_, "webhook-timestamp": ts, "webhook-signature": sig})
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
The secret is returned once as signing_secret when you create a webhook subscription, and it can't be retrieved later. It starts with whsec_. Keep the full value in LINQ_WEBHOOK_SECRET. Each subscription has its own secret. Handlers fail closed: with the variable unset they return 500 instead of accepting anything. A missing header or a bad signature returns 400.
Hookdeck retries and replays hit the 5-minute window
The Standard Webhooks libraries always enforce a fixed 5-minute timestamp window, and it can't be configured. Hookdeck retries and replays resend the original webhook-timestamp, so the library rejects any retry or replay that happens more than 5 minutes after the original delivery. If you verify behind Hookdeck (or replay old events), use the manual verifier in references/verification.md, where the window can be dropped or widened. Deduplicate on event_id instead of relying on the timestamp.
Common Event Types
76 events are documented, and the list may not be exhaustive. The name is event_type in the JSON body. The examples handle these:
| Event | Triggered When |
|---|---|
message.received | An incoming message arrives on your line |
message.sent | A message was successfully sent from your line |
message.delivered | The message reached the recipient's device (not sent for SMS/MMS) |
message.read | The recipient read the message (not sent for SMS/MMS) |
message.failed | Message delivery failed |
message.edited | A message was edited (2026-02-03 webhook version only) |
reaction.added | A reaction was added to a message part |
reaction.removed | A reaction was removed |
chat.created | A chat was created |
participant.added | Someone joined a group chat |
participant.removed | Someone left or was removed from a group chat |
phone_ | A line's status or reputation changed |
Linq also sends poll, chat, call, payment, location, contact card and account events (api_token.*, webhook_subscription.*, team_member.*). See references/overview.md for the full list and the payload envelope.
Environment Variables
# Signing secret of the webhook subscription (Standard Webhooks format, starts with whsec_)
LINQ_WEBHOOK_SECRET=whsec_xxxxx
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 linq --path /webhooks/linq
Create a webhook subscription whose target_url is the tunnel URL, then send a message to one of your lines to see an event arrive.
Reference Materials
- references/overview.md - Linq webhook concepts, events, payload envelope, versions, retries
- references/setup.md - Create a subscription through the API, get the secret, manage and rotate it
- references/verification.md - Standard Webhooks verification details, header fallback, manual verifier, timestamp window