API Reference
Libraries
API Overview
Chats
Create a new chat
List all chats
Get a chat by ID
Update a chat
Mark chat as read
Leave a group chat
Share your contact card with a chat
Send a voice memo to a chat
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
Remove a participant from a chat
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
Stop typing indicator
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.
Rich Link Previews
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
linkpart cannot be combined with other parts in the same message. - Maximum URL length: 2,048 characters.
Send a message to an existing chat
Get messages from a chat
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.
Rich Link Previews
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
linkpart cannot be combined with other parts in the same message. - Maximum URL length: 2,048 characters.
Get all messages in a thread
Get a message by ID
Delete a message from system
Add or remove a reaction to a message
Edit the content of a message part
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_idwithout re-downloading each time - Reduce message send latency — the file is already stored, so sending is faster
How it works:
POST /v3/attachmentswith file metadata → returns a presignedupload_url(valid for 15 minutes) and a permanentattachment_id- PUT the raw file bytes to the
upload_urlwith therequired_headers(no JSON or multipart — just the binary content) - Reference the
attachment_idin 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:
urlfields in media and voice memo message partsdownload_urlfields 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 (
urlfield): 10MB maximum - Pre-upload (
attachment_id): 100MB maximum
Security & Ownership
Every attachment is bound to the partner account that created or received it. The API enforces ownership on every operation that touches an attachment — sending, retrieving, deleting.
What this means for you:
- An attachment created under your API key can only be referenced by your API key.
- Submitting another partner’s
attachment_idreturns404 Not Found. We do not disclose whether the id exists or belongs to someone else. - Submitting a CDN URL that resolves to another partner’s attachment is rejected before the send is attempted.
- Ownership enforcement applies uniformly across send, create-chat, voice memo, retrieve, and delete operations.
Every attachment-affecting endpoint requires a valid partner API key. Unauthenticated calls return 401 Unauthorized.
Attachment URL Patterns
Attachment URLs in API responses and webhook payloads use one of two layouts, depending on the attachment’s tier:
| Tier | URL pattern | TTL |
|---|---|---|
| Persistent (default) | https://cdn.linqapp.com/attachments/partners/{partner_id}/{attachment_id}/{filename} | Long-lived |
| Ephemeral | Pre-signed URL pointing at the ephemeral prefix on cdn.linqapp.com | 15 minutes per signed URL — re-fetch via the API for a fresh URL |
Inbound media you receive over webhooks uses the same layout your outbound sends produce, so the URL you store and the URL you build look identical — no special casing in your client.
Ephemeral Attachments (Privacy Tier)
For regulated or sensitive content, opt in to the ephemeral attachments tier by contacting your Linq support contact. You can request it at two scopes:
| Scope | Effect |
|---|---|
| Partner-wide | Every outbound and inbound attachment on every phone number under your account is routed through the ephemeral tier. |
| Per phone number | Only the specified phone numbers route their attachments through the ephemeral tier. The rest stay on the persistent tier. |
Behavioral differences vs the persistent default:
| Aspect | Persistent | Ephemeral |
|---|---|---|
| Download URL form | Long-lived CDN URL | Pre-signed URL with short TTL |
| Retention floor | Indefinite (until you call DELETE) | Hard backstop: 1 day — even without an explicit DELETE, the platform removes the underlying bytes after 24 hours |
| URL re-fetch | Not required | Fetch via GET /v3/attachments/{attachmentId} for a fresh signed URL after TTL expiry |
| Cross-partner isolation | Enforced | Enforced |
When to choose ephemeral:
- Your downstream system processes the file immediately on receipt and does not need to re-read it later.
- You have a compliance requirement that the platform must not retain attachments beyond a short window.
- The content is high-sensitivity (PHI, financial documents, identity verification) and you do not want it sitting behind a long-lived URL.
Important: ephemeral applies in both directions — outbound files you upload and inbound media received by the phone numbers in that scope. Download bytes you need to keep promptly, or fetch a fresh signed URL via the API when needed.
Deleting an Attachment
To permanently remove an attachment you own, use:
DELETE /v3/attachments/{attachmentId}
Authorization: Bearer <your_api_key>
What this does:
- Verifies the attachment is owned by your account. Returns
404otherwise. - Removes the underlying file from Linq storage.
- Records an audit entry (timestamp, partner, attachment id).
Response codes:
| Status | Meaning |
|---|---|
204 No Content | Deletion succeeded. The attachment is removed from Linq storage. |
400 Bad Request | attachmentId is not a valid UUID. |
401 Unauthorized | Missing or invalid API key. |
404 Not Found | Attachment does not exist or is not owned by your account. |
500 Internal Server Error | Transient infrastructure issue — safe to retry. |
Effect on message history:
- Messages that referenced the deleted attachment remain visible.
- The message part that pointed at the attachment is preserved with no attachment reference.
- Webhook payloads previously delivered to you retain the original URL string, but downloads from that URL return
404going forward.
Deletion is irreversible. Once 204 is returned, the bytes are gone — there is no undelete.
Inbound Media Flow
When one of your phone numbers receives a message with media (image, video, audio, document), the platform:
- Stores the file under your partner account.
- Records metadata linked to the inbound message.
- Delivers a webhook whose
parts[]array includes amediapart with aurlpointing atcdn.linqapp.com. - If the receiving phone is opted in to ephemeral, the
urlis a short-TTL signed URL.
You can acknowledge the webhook without fetching the file inline, and lazy-load via GET /v3/attachments/{attachmentId} later. For ephemeral attachments, retrieving via the API always returns a freshly-signed URL.
Data Lifecycle Summary
| Data | Persistent tier | Ephemeral tier |
|---|---|---|
| Attachment bytes | Retained until you DELETE | Auto-removed after 1 day, also removable via DELETE |
| Attachment metadata (id, filename, mime type, size) | Retained until you DELETE | Removed alongside the bytes |
| Message body & parts | Retained per message-retention policy | Retained per message-retention policy |
| Audit log of deletions | Retained per platform retention policy | Retained per platform retention policy |
In transit: TLS 1.2+ everywhere. At rest: AES-256 (server-side encryption).
Compliance Checklist
If you’re integrating Linq under a security or privacy review, here is the short list:
- Allowlist exactly one outbound domain:
cdn.linqapp.com. - Decide whether you need ephemeral attachments (high-sensitivity content) — request enablement through your Linq support contact.
- Implement
DELETE /v3/attachments/{attachmentId}calls in your deletion workflow. - Persist any attachments your application needs long-term — Linq is the authoritative source until you delete, but the ephemeral tier auto-purges after 1 day.
- For audit: every deletion is logged on Linq’s side. Surface a confirmation in your application UI based on the
204response. - For end-user “right to delete” requests: enumerate attachment ids and
DELETEeach. The platform does not provide a partner-wide wipe endpoint — deletion is per-attachment by design.
Pre-upload a file
Get attachment metadata
Delete an attachment
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.
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
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:
| Header | Description |
|---|---|
X-Webhook-Event | The event type (e.g., message.sent, message.received) |
X-Webhook-Subscription-ID | Your webhook subscription ID |
X-Webhook-Timestamp | Unix timestamp (seconds) when the webhook was sent |
X-Webhook-Signature | HMAC-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:
timestampis the value from theX-Webhook-Timestampheaderpayloadis the raw JSON request body (exact bytes, not re-serialized)
Verification Steps:
- Extract the
X-Webhook-TimestampandX-Webhook-Signatureheaders - Get the raw request body bytes (do not parse and re-serialize)
- Concatenate:
"{timestamp}.{payload}" - Compute HMAC-SHA256 using your signing secret as the key
- Hex-encode the result and compare with
X-Webhook-Signature - 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
List available webhook event types
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:
| Header | Description |
|---|---|
X-Webhook-Event | The event type (e.g., message.sent, message.received) |
X-Webhook-Subscription-ID | Your webhook subscription ID |
X-Webhook-Timestamp | Unix timestamp (seconds) when the webhook was sent |
X-Webhook-Signature | HMAC-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:
timestampis the value from theX-Webhook-Timestampheaderpayloadis the raw JSON request body (exact bytes, not re-serialized)
Verification Steps:
- Extract the
X-Webhook-TimestampandX-Webhook-Signatureheaders - Get the raw request body bytes (do not parse and re-serialize)
- Concatenate:
"{timestamp}.{payload}" - Compute HMAC-SHA256 using your signing secret as the key
- Hex-encode the result and compare with
X-Webhook-Signature - 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
List all webhook subscriptions
Get a webhook subscription by ID
Update a webhook subscription
Delete a webhook subscription
Capability
Check whether a recipient address supports iMessage or RCS before sending a message.
Check iMessage capability
Check RCS capability
Webhooks
Events
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.