Skip to content
Get started
V2 Reference
Webhook Events Documentation

Webhook Events Reference (Documentation Only)

GET
/webhooks/events
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 TypeDescriptionWhen Triggered
message.sentMessage sent from LinqWhen a message is sent from a Linq user
message.receivedMessage received by LinqWhen a message is received by a Linq user
message.readMessage marked as readWhen messages are marked as read
call.completedCall completedWhen a voice call has been completed
reaction.sentReaction sent by a Linq numberWhen a Linq number sends a reaction to a message
reaction.receivedReaction received from another partyWhen a reaction is received from another party
typing_indicator.receivedTyping indicator displayedWhen a typing indicator is displayed
typing_indicator.removedTyping indicator removedWhen a typing indicator is removed
chat.createdChat createdWhen a new chat is created via the API
contact.createdContact createdWhen a contact is created via the API
contact.updatedContact updatedWhen a contact is updated via the API
contact.deletedContact deletedWhen a contact is deleted via the API
participant.addedParticipant added to group chatWhen someone is added to a group conversation
participant.removedParticipant removed from group chatWhen 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 webhooks
  • data: 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:

  1. Extract the signature from the X-Webhook-Signature header
  2. Compute the expected signature using HMAC-SHA256 with your webhook secret and the raw request body
  3. 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 reaction
  • like - Like/thumbs up reaction
  • dislike - Dislike/thumbs down reaction
  • laugh - Laugh/haha reaction
  • emphasize - Emphasize/!! reaction
  • question - 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

  1. Create a webhook endpoint on your server that can receive POST requests
  2. Create a webhook subscription using POST /api/partner/v2/webhook_subscriptions
  3. Store your webhook secret securely for signature verification
  4. Verify signatures on all incoming webhooks
  5. Respond with HTTP 200 within 10 seconds to acknowledge receipt
  6. 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

This endpoint is for documentation purposes only