Webhook Events Reference (Documentation Only)
const url = 'https://api.linqapp.com/webhooks/events';const options = { method: 'GET', headers: {'X-LINQ-INTEGRATION-TOKEN': '<X-LINQ-INTEGRATION-TOKEN>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://api.linqapp.com/webhooks/events \ --header 'X-LINQ-INTEGRATION-TOKEN: <X-LINQ-INTEGRATION-TOKEN>'Note: This is a documentation endpoint only - it does not exist in the actual API
Webhook Events Overview
Linq V2 provides real-time event notifications via webhooks. When specific events occur in your Linq account, we send HTTP POST requests to your configured endpoint.
Available Webhook Events
| Event Type | Description | When Triggered |
|---|---|---|
message.sent | Message sent from Linq | When a message is sent from a Linq user |
message.received | Message received by Linq | When a message is received by a Linq user |
message.read | Message marked as read | When messages are marked as read |
call.completed | Call completed | When a voice call has been completed |
reaction.sent | Reaction sent by a Linq number | When a Linq number sends a reaction to a message |
reaction.received | Reaction received from another party | When a reaction is received from another party |
typing_indicator.received | Typing indicator displayed | When a typing indicator is displayed |
typing_indicator.removed | Typing indicator removed | When a typing indicator is removed |
chat.created | Chat created | When a new chat is created via the API |
contact.created | Contact created | When a contact is created via the API |
contact.updated | Contact updated | When a contact is updated via the API |
contact.deleted | Contact deleted | When a contact is deleted via the API |
participant.added | Participant added to group chat | When someone is added to a group conversation |
participant.removed | Participant removed from group chat | When someone leaves or is removed from a group conversation |
Webhook Payload Structure
All webhook events follow this standard format:
{
"api_version": "v2",
"created_at": "2025-06-01T10:00:00-06:00",
"data": {
// Event-specific data
},
"event_id": "9dabceb9-9194-4dc6-beda-892573f377b4",
"event_type": "message.sent"
}
Field Descriptions:
event_type: The type of webhook event (e.g., “message.sent”, “message.received”)api_version: Always “v2” for V2 webhooksdata: Event-specific payload data (structure varies by event type)created_at: Timestamp when the event was created (ISO 8601 format with timezone)event_id: Unique identifier for this webhook event (UUID format)
Webhook Signature Verification
Linq signs all webhook requests with a secret key to allow you to verify that webhooks are genuinely from Linq. When creating a webhook subscription, you can optionally provide a secret value. If provided, Linq will include an X-Webhook-Signature header with each webhook request.
Signature Format
The signature is computed using HMAC-SHA256:
X-Webhook-Signature: sha256=<hex_digest>
Verifying Signatures
To verify a webhook signature:
- Extract the signature from the
X-Webhook-Signatureheader - Compute the expected signature using HMAC-SHA256 with your webhook secret and the raw request body
- Compare the computed signature with the received signature
Important: The signature is computed on the canonical JSON format (with keys sorted recursively). Make sure to use the raw request body exactly as received.
Example Verification (Ruby)
require 'openssl'
require 'json'
def verify_webhook_signature(secret, payload, signature_header)
# Extract the hex digest from the header
expected_signature = signature_header.sub(/^sha256=/, '')
# Compute HMAC-SHA256 of the raw payload
computed_signature = OpenSSL::HMAC.hexdigest('SHA256', secret, payload)
# Compare signatures securely
ActiveSupport::SecurityUtils.secure_compare(computed_signature, expected_signature)
end
# Usage in your webhook endpoint
secret = 'your_webhook_secret'
payload = request.raw_post
signature = request.headers['X-Webhook-Signature']
if verify_webhook_signature(secret, payload, signature)
# Signature is valid, process webhook
else
# Invalid signature, reject request
render json: { error: 'Invalid signature' }, status: :unauthorized
end
Example Verification (Node.js)
const crypto = require('crypto');
function verifyWebhookSignature(secret, payload, signatureHeader) {
// Extract the hex digest from the header
const expectedSignature = signatureHeader.replace(/^sha256=/, '');
// Compute HMAC-SHA256 of the raw payload
const computedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Compare signatures securely (constant-time comparison)
return crypto.timingSafeEqual(
Buffer.from(computedSignature),
Buffer.from(expectedSignature)
);
}
// Usage in your webhook endpoint
const secret = 'your_webhook_secret';
const payload = req.rawBody; // Raw request body as string
const signature = req.headers['x-webhook-signature'];
if (verifyWebhookSignature(secret, payload, signature)) {
// Signature is valid, process webhook
} else {
// Invalid signature, reject request
res.status(401).json({ error: 'Invalid signature' });
}
Event Payload Examples
Message Sent/Received Event
{
"api_version": "v2",
"created_at": "2025-05-21T15:30:00-06:00",
"data": {
"id": "27799380",
"chat_id": "324324",
"text": "Hello, how are you?",
"sent_at": "2025-05-21 15:30:00 -0600",
"is_read": false,
"from_phone": "+15551234567",
"service": "iMessage",
"reaction_id": null,
"chat_handles": [
{
"identifier": "+15551234567",
"display_name": "John Doe",
"is_me": false
},
{
"identifier": "+15559876543",
"display_name": "Your Linq Number",
"is_me": true
}
],
"attachments": [
{
"id": "abc12345-1234-5678-9abc-def012345678",
"url": "https://storage.googleapis.com/linq-files/attachments/abc123.pdf",
"filename": "document.pdf",
"mime_type": "application/pdf"
}
]
},
"event_id": "9dabceb9-9194-4dc6-beda-892573f377b4",
"event_type": "message.received"
}
Message Read Event
{
"api_version": "v2",
"created_at": "2025-05-21T15:35:00-06:00",
"data": {
"id": "12345",
"chat_id": "67890",
"read_at": "2025-05-21T15:35:00-06:00"
},
"event_id": "wh_evt_789ghi",
"event_type": "message.read"
}
Call Completed Event
{
"api_version": "v2",
"created_at": "2025-05-21T15:05:30-06:00",
"data": {
"id": "12345",
"to": "+15559876543",
"from": "+15551234567",
"status": "completed",
"direction": "inbound",
"start_time": "2025-05-21 15:00:00 -0600",
"end_time": "2025-05-21 15:05:30 -0600",
"duration": 330,
"call_type": "normal",
"forwarded_from": null,
"answered_by": "+15559876543",
"voicemail": false,
"has_recording": true,
"recording_data": {
"id": "67890",
"url": "https://storage.googleapis.com/linq-files/recordings/abc123.mp3",
"duration": 330,
"transcribed": true,
"transcript_available": true,
"transcription": "Hello, this is John calling about our meeting tomorrow...",
"summary": "John called to confirm tomorrow's meeting at 2pm."
}
},
"event_id": "wh_evt_101jkl",
"event_type": "call.completed"
}
Contact Created/Updated Event
{
"api_version": "v2",
"created_at": "2025-07-30T10:00:00-06:00",
"data": {
"id": 123,
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"email": "[email protected]",
"phone_number": "+15551234567",
"company": "Acme Corp",
"title": "CEO",
"location": "San Francisco, CA",
"image_url": null,
"created_at": "2025-07-30T10:00:00-06:00",
"updated_at": "2025-07-30T10:00:00-06:00",
"contact_owner": {
"id": 456,
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Owner",
"name": "Jane Owner"
}
},
"event_id": "wh_evt_contact_123",
"event_type": "contact.created"
}
Contact Deleted Event
{
"api_version": "v2",
"created_at": "2025-07-30T10:00:00-06:00",
"data": {
"id": 123
},
"event_id": "wh_evt_contact_deleted_123",
"event_type": "contact.deleted"
}
Note: The contact.deleted event only includes the contact ID, not the full contact data or owner information.
Chat Created Event
{
"api_version": "v2",
"created_at": "2025-05-21T15:30:00-06:00",
"data": {
"chat_id": "67890",
"service": "iMessage",
"is_direct": true,
"is_group": false,
"display_name": "John Doe",
"chat_handles": [
{
"id": "123",
"identifier": "+15551234567",
"display_name": "John Doe",
"is_me": false,
"is_departed": false
},
{
"id": "124",
"identifier": "+15559876543",
"display_name": "Your Linq Number",
"is_me": true,
"is_departed": false
}
],
"created_at": "2025-05-21T15:30:00-06:00"
},
"event_id": "wh_evt_chat_123",
"event_type": "chat.created"
}
Reaction Sent/Received Event
The reaction field supports the following values:
love- Love/heart reactionlike- Like/thumbs up reactiondislike- Dislike/thumbs down reactionlaugh- Laugh/haha reactionemphasize- Emphasize/!! reactionquestion- Question/? reaction
{
"api_version": "v2",
"created_at": "2025-07-10T15:31:00-06:00",
"data": {
"id": "12346",
"chat_message_id": "67891",
"chat_id": "11111",
"reaction": "like",
"is_from_me": false,
"sent_at": "2025-07-10 15:31:00 -0600",
"created_at": "2025-07-10 15:31:00 -0600",
"chat_handle": {
"id": "22223",
"identifier": "+15551234567"
},
"chat_handles": [
{
"identifier": "+15551234567",
"display_name": "John Doe",
"is_me": false
},
{
"identifier": "+15559876543",
"display_name": "Your Linq Number",
"is_me": true
}
],
"associated_message": {
"id": "67891",
"text": "I'm doing great, thanks!",
"sent_at": "2025-07-10 15:30:30 -0600"
}
},
"event_id": "wh_evt_456deg",
"event_type": "reaction.received"
}
Typing Indicator Received/Removed Event
{
"api_version": "v2",
"created_at": "2025-07-10T15:32:00-06:00",
"data": {
"chat_id": "11111",
"display": true,
"timestamp": "2025-07-10 15:32:00 -0600",
"chat_handles": [
{
"identifier": "+15551234567",
"display_name": "John Doe",
"is_me": false
},
{
"identifier": "+15559876543",
"display_name": "Your Linq Number",
"is_me": true
}
]
},
"event_id": "wh_evt_789ghi",
"event_type": "typing_indicator.received"
}
Webhook Security
Signature Verification
All webhooks include a signature in the X-Webhook-Signature header. Verify the signature to ensure the webhook is from Linq:
const crypto = require('crypto');
function verifyWebhookSignature(body, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return `sha256=${expectedSignature}` === signature;
}
Python Example
import hmac
import hashlib
def verify_webhook(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return f"sha256={expected}" == signature
Integration Steps
- Create a webhook endpoint on your server that can receive POST requests
- Create a webhook subscription using
POST /api/partner/v2/webhook_subscriptions - Store your webhook secret securely for signature verification
- Verify signatures on all incoming webhooks
- Respond with HTTP 200 within 10 seconds to acknowledge receipt
- Process webhooks asynchronously to avoid timeouts
Testing Webhooks
Use tools like ngrok or webhook.site to test webhook integrations during development:
# Using ngrok
ngrok http 3000
# Then use the ngrok URL as your webhook endpoint
Error Handling
Your webhook endpoint should:
- Return HTTP 200 for successful receipt
- Return appropriate error codes (400, 500) for failures
- Log all webhook events for debugging
- Implement idempotency to handle duplicate events
Authorizations
Section titled “Authorizations ”Responses
Section titled “ Responses ”This endpoint is for documentation purposes only