---
title: Webhook Events Reference (Documentation Only) | API Docs
---

GET

/webhooks/events

Select code sample cURL (shell:curl)

```
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>'
```

- Production server api.linqapp.com/webhooks/events

**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 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": "john@example.com",
    "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": "owner@company.com",
      "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

## Authorizations

- **[ApiKeyAuth](/v2/api/#apikeyauth/index.md)**

## Responses

### 200

This endpoint is for documentation purposes only
