Skip to content
V2 (Legacy) API ReferenceGet started

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
client.webhookSubscriptions.create(WebhookSubscriptionCreateParams { subscribed_events, target_url, phone_numbers } body, RequestOptionsoptions?): WebhookSubscriptionCreateResponse { id, created_at, is_active, 5 more }
POST/v3/webhook-subscriptions
List all webhook subscriptions
client.webhookSubscriptions.list(RequestOptionsoptions?): WebhookSubscriptionListResponse { subscriptions }
GET/v3/webhook-subscriptions
Get a webhook subscription by ID
client.webhookSubscriptions.retrieve(stringsubscriptionID, RequestOptionsoptions?): WebhookSubscription { id, created_at, is_active, 4 more }
GET/v3/webhook-subscriptions/{subscriptionId}
Update a webhook subscription
client.webhookSubscriptions.update(stringsubscriptionID, WebhookSubscriptionUpdateParams { is_active, phone_numbers, subscribed_events, target_url } body, RequestOptionsoptions?): WebhookSubscription { id, created_at, is_active, 4 more }
PUT/v3/webhook-subscriptions/{subscriptionId}
Delete a webhook subscription
client.webhookSubscriptions.delete(stringsubscriptionID, RequestOptionsoptions?): void
DELETE/v3/webhook-subscriptions/{subscriptionId}
ModelsExpand Collapse
WebhookSubscription { id, created_at, is_active, 4 more }
id: string

Unique identifier for the webhook subscription

created_at: string

When the subscription was created

formatdate-time
is_active: boolean

Whether this subscription is currently active

subscribed_events: Array<WebhookEventType>

List of event types this subscription receives

One of the following:
"message.sent"
"message.received"
"message.read"
"message.delivered"
"message.failed"
"message.edited"
"reaction.added"
"reaction.removed"
"participant.added"
"participant.removed"
"chat.created"
"chat.group_name_updated"
"chat.group_icon_updated"
"chat.group_name_update_failed"
"chat.group_icon_update_failed"
"chat.typing_indicator.started"
"chat.typing_indicator.stopped"
"phone_number.status_updated"
"call.initiated"
"call.ringing"
"call.answered"
"call.ended"
"call.failed"
"call.declined"
"call.no_answer"
target_url: string

URL where webhook events will be sent

formaturi
updated_at: string

When the subscription was last updated

formatdate-time
phone_numbers?: Array<string> | null

Phone numbers this subscription filters for. If null or empty, events from all phone numbers are delivered.

WebhookSubscriptionCreateResponse { id, created_at, is_active, 5 more }

Response returned when creating a webhook subscription. Includes the signing secret which is only shown once.

id: string

Unique identifier for the webhook subscription

created_at: string

When the subscription was created

formatdate-time
is_active: boolean

Whether this subscription is currently active

signing_secret: string

Secret for verifying webhook signatures. Store this securely - it cannot be retrieved again.

subscribed_events: Array<WebhookEventType>

List of event types this subscription receives

One of the following:
"message.sent"
"message.received"
"message.read"
"message.delivered"
"message.failed"
"message.edited"
"reaction.added"
"reaction.removed"
"participant.added"
"participant.removed"
"chat.created"
"chat.group_name_updated"
"chat.group_icon_updated"
"chat.group_name_update_failed"
"chat.group_icon_update_failed"
"chat.typing_indicator.started"
"chat.typing_indicator.stopped"
"phone_number.status_updated"
"call.initiated"
"call.ringing"
"call.answered"
"call.ended"
"call.failed"
"call.declined"
"call.no_answer"
target_url: string

URL where webhook events will be sent

formaturi
updated_at: string

When the subscription was last updated

formatdate-time
phone_numbers?: Array<string> | null

Phone numbers this subscription filters for. If null or empty, events from all phone numbers are delivered.

WebhookSubscriptionListResponse { subscriptions }
subscriptions: Array<WebhookSubscription { id, created_at, is_active, 4 more } >

List of webhook subscriptions

id: string

Unique identifier for the webhook subscription

created_at: string

When the subscription was created

formatdate-time
is_active: boolean

Whether this subscription is currently active

subscribed_events: Array<WebhookEventType>

List of event types this subscription receives

One of the following:
"message.sent"
"message.received"
"message.read"
"message.delivered"
"message.failed"
"message.edited"
"reaction.added"
"reaction.removed"
"participant.added"
"participant.removed"
"chat.created"
"chat.group_name_updated"
"chat.group_icon_updated"
"chat.group_name_update_failed"
"chat.group_icon_update_failed"
"chat.typing_indicator.started"
"chat.typing_indicator.stopped"
"phone_number.status_updated"
"call.initiated"
"call.ringing"
"call.answered"
"call.ended"
"call.failed"
"call.declined"
"call.no_answer"
target_url: string

URL where webhook events will be sent

formaturi
updated_at: string

When the subscription was last updated

formatdate-time
phone_numbers?: Array<string> | null

Phone numbers this subscription filters for. If null or empty, events from all phone numbers are delivered.