Skip to content
V2 (Legacy) API ReferenceGet started

API Reference

Libraries

npm install @linqapp/sdk
pip install linq-python
go get -u 'github.com/linq-team/[email protected]'

API Overview

Chats

Create a new chat
POST/v3/chats
List all chats
GET/v3/chats
Get a chat by ID
GET/v3/chats/{chatId}
Update a chat
PUT/v3/chats/{chatId}
Mark chat as read
POST/v3/chats/{chatId}/read
Leave a group chat
POST/v3/chats/{chatId}/leave
Share your contact card with a chat
POST/v3/chats/{chatId}/share_contact_card
Send a voice memo to a chat
POST/v3/chats/{chatId}/voicememo

ChatsParticipants

A Chat is a conversation thread with one or more participants.

To begin a chat, you must create a Chat with at least one recipient handle. Including multiple handles creates a group chat.

When creating a chat, the from field specifies which of your authorized phone numbers the message originates from. Your authentication token grants access to one or more phone numbers, but the from field determines the actual sender.

Handle Format:

  • Handles can be phone numbers or email addresses
  • Phone numbers MUST be in E.164 format (starting with +)
  • Phone format: +[country code][subscriber number]
  • Example phone: +12223334444 (US), +442071234567 (UK), +81312345678 (Japan)
  • Example email: [email protected]
  • No spaces, dashes, or parentheses in phone numbers
Add a participant to a chat
POST/v3/chats/{chatId}/participants
Remove a participant from a chat
DELETE/v3/chats/{chatId}/participants

ChatsTyping

A Chat is a conversation thread with one or more participants.

To begin a chat, you must create a Chat with at least one recipient handle. Including multiple handles creates a group chat.

When creating a chat, the from field specifies which of your authorized phone numbers the message originates from. Your authentication token grants access to one or more phone numbers, but the from field determines the actual sender.

Handle Format:

  • Handles can be phone numbers or email addresses
  • Phone numbers MUST be in E.164 format (starting with +)
  • Phone format: +[country code][subscriber number]
  • Example phone: +12223334444 (US), +442071234567 (UK), +81312345678 (Japan)
  • Example email: [email protected]
  • No spaces, dashes, or parentheses in phone numbers
Start typing indicator
POST/v3/chats/{chatId}/typing
Stop typing indicator
DELETE/v3/chats/{chatId}/typing

ChatsMessages

Messages are individual communications within a chat thread.

Messages can include text, media attachments, rich link previews, special effects (like confetti or fireworks), and reactions. All messages are associated with a specific chat and sent from a phone number you own.

Messages support delivery status tracking, read receipts, and editing capabilities.

Send a URL as a link part to deliver it with a rich preview card showing the page’s title, description, and image (when available). A link part must be the only part in the message — it cannot be combined with text or media parts. To send a URL without a preview card, include it in a text part instead.

Limitations:

  • A link part cannot be combined with other parts in the same message.
  • Maximum URL length: 2,048 characters.
Send a message to an existing chat
POST/v3/chats/{chatId}/messages
Get messages from a chat
GET/v3/chats/{chatId}/messages

Messages

Messages are individual communications within a chat thread.

Messages can include text, media attachments, rich link previews, special effects (like confetti or fireworks), and reactions. All messages are associated with a specific chat and sent from a phone number you own.

Messages support delivery status tracking, read receipts, and editing capabilities.

Send a URL as a link part to deliver it with a rich preview card showing the page’s title, description, and image (when available). A link part must be the only part in the message — it cannot be combined with text or media parts. To send a URL without a preview card, include it in a text part instead.

Limitations:

  • A link part cannot be combined with other parts in the same message.
  • Maximum URL length: 2,048 characters.
Get all messages in a thread
GET/v3/messages/{messageId}/thread
Get a message by ID
GET/v3/messages/{messageId}
Delete a message from system
DELETE/v3/messages/{messageId}
Add or remove a reaction to a message
POST/v3/messages/{messageId}/reactions
Edit the content of a message part
PATCH/v3/messages/{messageId}

Attachments

Send files (images, videos, documents, audio) with messages by providing a URL in a media part. Pre-uploading via POST /v3/attachments is optional and only needed for specific optimization scenarios.

Sending Media via URL (up to 10MB)

Provide a publicly accessible HTTPS URL with a supported media type in the url field of a media part.

{
  "parts": [
    { "type": "media", "url": "https://your-cdn.com/images/photo.jpg" }
  ]
}

This works with any URL you already host — no pre-upload step required. Maximum file size: 10MB.

Pre-Upload (required for files over 10MB)

Use POST /v3/attachments when you want to:

  • Send files larger than 10MB (up to 100MB) — URL-based downloads are limited to 10MB
  • Send the same file to many recipients — upload once, reuse the attachment_id without re-downloading each time
  • Reduce message send latency — the file is already stored, so sending is faster

How it works:

  1. POST /v3/attachments with file metadata → returns a presigned upload_url (valid for 15 minutes) and a permanent attachment_id
  2. PUT the raw file bytes to the upload_url with the required_headers (no JSON or multipart — just the binary content)
  3. Reference the attachment_id in your media part when sending messages (no expiration)

Key difference: When you provide an external url, we download and process the file on every send. When you use a pre-uploaded attachment_id, the file is already stored — so repeated sends skip the download step entirely.

Domain Allowlisting

Attachment URLs in API responses are served from cdn.linqapp.com. This includes:

  • url fields in media and voice memo message parts
  • download_url fields in attachment and upload response objects

If your application enforces domain allowlists (e.g., for SSRF protection), add:

cdn.linqapp.com

Supported File Types

  • Images: JPEG, PNG, GIF, HEIC, HEIF, TIFF, BMP
  • Videos: MP4, MOV, M4V
  • Audio: M4A, AAC, MP3, WAV, AIFF, CAF, AMR
  • Documents: PDF, TXT, RTF, CSV, Office formats, ZIP
  • Contact & Calendar: VCF, ICS

Audio: Attachment vs Voice Memo

Audio files sent as media parts appear as downloadable file attachments in iMessage. To send audio as an iMessage voice memo bubble (with native inline playback UI), use the dedicated POST /v3/chats/{chatId}/voicememo endpoint instead.

File Size Limits

  • URL-based (url field): 10MB maximum
  • Pre-upload (attachment_id): 100MB maximum
Pre-upload a file
POST/v3/attachments
Get attachment metadata
GET/v3/attachments/{attachmentId}

Phonenumbers

Phone Numbers represent the phone numbers assigned to your partner account.

Use the list phone numbers endpoint to discover which phone numbers are available for sending messages.

When creating chats, listing chats, or sending a voice memo, use one of your assigned phone numbers in the from field.

List phone numbers (deprecated)
Deprecated
GET/v3/phonenumbers

Phone Numbers

Phone Numbers represent the phone numbers assigned to your partner account.

Use the list phone numbers endpoint to discover which phone numbers are available for sending messages.

When creating chats, listing chats, or sending a voice memo, use one of your assigned phone numbers in the from field.

List phone numbers
GET/v3/phone_numbers

Webhook Events

Webhook Subscriptions allow you to receive real-time notifications when events occur on your account.

Configure webhook endpoints to receive events such as messages sent/received, delivery status changes, reactions, typing indicators, and more.

Failed deliveries (5xx, 429, network errors) are retried up to 10 times over ~25 minutes with exponential backoff. Each event includes a unique ID for deduplication.

Webhook Headers

Each webhook request includes the following headers:

HeaderDescription
X-Webhook-EventThe event type (e.g., message.sent, message.received)
X-Webhook-Subscription-IDYour webhook subscription ID
X-Webhook-TimestampUnix timestamp (seconds) when the webhook was sent
X-Webhook-SignatureHMAC-SHA256 signature for verification

Verifying Webhook Signatures

All webhooks are signed using HMAC-SHA256. You should always verify the signature to ensure the webhook originated from Linq and hasn’t been tampered with.

Signature Construction:

The signature is computed over a concatenation of the timestamp and payload:

{timestamp}.{payload}

Where:

  • timestamp is the value from the X-Webhook-Timestamp header
  • payload is the raw JSON request body (exact bytes, not re-serialized)

Verification Steps:

  1. Extract the X-Webhook-Timestamp and X-Webhook-Signature headers
  2. Get the raw request body bytes (do not parse and re-serialize)
  3. Concatenate: "{timestamp}.{payload}"
  4. Compute HMAC-SHA256 using your signing secret as the key
  5. Hex-encode the result and compare with X-Webhook-Signature
  6. Use constant-time comparison to prevent timing attacks

Example (Python):

import hmac
import hashlib

def verify_webhook(signing_secret, payload, timestamp, signature):
    message = f"{timestamp}.{payload.decode('utf-8')}"
    expected = hmac.new(
        signing_secret.encode('utf-8'),
        message.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Example (Node.js):

const crypto = require('crypto');

function verifyWebhook(signingSecret, payload, timestamp, signature) {
  const message = `${timestamp}.${payload}`;
  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(message)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Security Best Practices:

  • Reject webhooks with timestamps older than 5 minutes to prevent replay attacks
  • Always use constant-time comparison for signature verification
  • Store your signing secret securely (e.g., environment variable, secrets manager)
  • Return a 2xx status code quickly, then process the webhook asynchronously

Webhook Subscriptions

Webhook Subscriptions allow you to receive real-time notifications when events occur on your account.

Configure webhook endpoints to receive events such as messages sent/received, delivery status changes, reactions, typing indicators, and more.

Failed deliveries (5xx, 429, network errors) are retried up to 10 times over ~25 minutes with exponential backoff. Each event includes a unique ID for deduplication.

Webhook Headers

Each webhook request includes the following headers:

HeaderDescription
X-Webhook-EventThe event type (e.g., message.sent, message.received)
X-Webhook-Subscription-IDYour webhook subscription ID
X-Webhook-TimestampUnix timestamp (seconds) when the webhook was sent
X-Webhook-SignatureHMAC-SHA256 signature for verification

Verifying Webhook Signatures

All webhooks are signed using HMAC-SHA256. You should always verify the signature to ensure the webhook originated from Linq and hasn’t been tampered with.

Signature Construction:

The signature is computed over a concatenation of the timestamp and payload:

{timestamp}.{payload}

Where:

  • timestamp is the value from the X-Webhook-Timestamp header
  • payload is the raw JSON request body (exact bytes, not re-serialized)

Verification Steps:

  1. Extract the X-Webhook-Timestamp and X-Webhook-Signature headers
  2. Get the raw request body bytes (do not parse and re-serialize)
  3. Concatenate: "{timestamp}.{payload}"
  4. Compute HMAC-SHA256 using your signing secret as the key
  5. Hex-encode the result and compare with X-Webhook-Signature
  6. Use constant-time comparison to prevent timing attacks

Example (Python):

import hmac
import hashlib

def verify_webhook(signing_secret, payload, timestamp, signature):
    message = f"{timestamp}.{payload.decode('utf-8')}"
    expected = hmac.new(
        signing_secret.encode('utf-8'),
        message.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Example (Node.js):

const crypto = require('crypto');

function verifyWebhook(signingSecret, payload, timestamp, signature) {
  const message = `${timestamp}.${payload}`;
  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(message)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Security Best Practices:

  • Reject webhooks with timestamps older than 5 minutes to prevent replay attacks
  • Always use constant-time comparison for signature verification
  • Store your signing secret securely (e.g., environment variable, secrets manager)
  • Return a 2xx status code quickly, then process the webhook asynchronously
Create a new webhook subscription
POST/v3/webhook-subscriptions
List all webhook subscriptions
GET/v3/webhook-subscriptions
Get a webhook subscription by ID
GET/v3/webhook-subscriptions/{subscriptionId}
Update a webhook subscription
PUT/v3/webhook-subscriptions/{subscriptionId}
Delete a webhook subscription
DELETE/v3/webhook-subscriptions/{subscriptionId}

Capability

Check whether a recipient address supports iMessage or RCS before sending a message.

Check iMessage capability
POST/v3/capability/check_imessage
Check RCS capability
POST/v3/capability/check_rcs

Webhooks

Events
Function

Contact Card

Contact Card lets you set and share your contact information (name and profile photo) with chat participants via iMessage Name and Photo Sharing.

Use POST /v3/contact_card to create or update a card for a phone number. Use PATCH /v3/contact_card to update an existing active card. Use GET /v3/contact_card to retrieve the active card(s) for your partner account.

Sharing behavior: Sharing may not take effect in every chat due to limitations outside our control. We recommend calling the share endpoint once per day, after the first outbound activity.

Get contact cards
GET/v3/contact_card
Setup contact card
POST/v3/contact_card
Update contact card
PATCH/v3/contact_card