> For the complete documentation index, see [llms.txt](https://integrate.lexamica.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://integrate.lexamica.com/4.-webhook-subscriptions.md).

# Webhook Subscriptions

How to receive real-time event notifications FROM Lexamica.

***

## Table of Contents

1. [Overview](#-overview)
2. [Key Terms](#-key-terms)
3. [How It Works](#️-how-it-works)
4. [Available Events](#-available-events)
5. [Common Use Cases](#-common-use-cases)
6. [FAQ](#-faq)
7. [Technical Reference](#-technical-reference)

***

## 🎯 Overview

> **📌 TL;DR**
>
> Webhook subscriptions push events to your server in real-time. When something happens in Lexamica (case created, invitation accepted, etc.), we POST the data to your URL—already transformed to your field format.

Webhook subscriptions let you **receive events FROM Lexamica** the moment they happen. Instead of polling for updates, you register a URL and we'll notify you:

* When cases are created or updated
* When invitations are sent, accepted, or declined
* When the Relay Engine matches or rejects cases
* When files are uploaded
* And 20+ other event types

Each subscription targets a specific event type and includes your mapping, so data arrives in your format.

***

## 📖 Key Terms

| Term                     | Definition                                                                         |
| ------------------------ | ---------------------------------------------------------------------------------- |
| **Webhook Subscription** | A configuration that tells Lexamica where to send a specific event type.           |
| **Event**                | Something that happened in Lexamica (e.g., "Case Created", "Invitation Accepted"). |
| **Payload**              | The data sent with each webhook, transformed via your mapping.                     |
| **Signature**            | An HMAC-SHA256 hash that verifies the webhook came from Lexamica.                  |
| **Secret**               | A key you provide when creating the subscription, used to generate signatures.     |

***

## ⚙️ How It Works

When an event occurs in Lexamica:

```
Webhook Delivery Flow
─────────────────────

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│    Lexamica     │────▶│   Subscription  │────▶│  Your Endpoint  │
│                 │     │    Processor    │     │                 │
│ Event occurs:   │     │                 │     │ POST received   │
│ "Case Created"  │     │ 1. Find subs    │     │ with your field │
│                 │     │ 2. Apply mapping│     │ names           │
│                 │     │ 3. Sign payload │     │                 │
│                 │     │ 4. Queue delivery     │ Return 200 OK   │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                                │
                                ▼
                        ┌─────────────────┐
                        │  Retry Logic    │
                        │                 │
                        │ If failed:      │
                        │ • +30 seconds   │
                        │ • +2 minutes    │
                        │ • +5 minutes    │
                        │ • +15 minutes   │
                        │ • Mark failed   │
                        └─────────────────┘
```

**Key points:**

1. You create a subscription for the events you care about
2. When that event happens, we transform the data using your mapping
3. We sign the payload with your secret and POST it to your URL
4. If delivery fails, we retry with exponential backoff

> **🎯 Key Takeaway**
>
> Return `200 OK` quickly and process the data asynchronously. Slow responses can cause timeouts and unnecessary retries.

***

## 💡 Available Events

### Case Events

| Event                 | Triggered When                         |
| --------------------- | -------------------------------------- |
| `Case Created`        | A new case is created in the system    |
| `Case Received`       | Your firm receives a case (as handler) |
| `Case Edited`         | Case details are modified              |
| `Case Fields Updated` | Specific fields on a case change       |
| `Case Stage Changed`  | Case moves to a new stage              |
| `Case Deleted`        | A case is deleted                      |

### Invitation Events

| Event                                         | Triggered When                           |
| --------------------------------------------- | ---------------------------------------- |
| `Case Invitation Sent`                        | An invitation is sent to a firm          |
| `Case Invitation Accepted`                    | A firm accepts an invitation             |
| `Case Invitation Declined`                    | A firm declines an invitation            |
| `Case Invitation Cancelled`                   | The originator cancels an invitation     |
| `Case Invitation Expired`                     | An invitation passes its expiration date |
| `Case Invitation Evaluated`                   | A firm marks invitation as "evaluating"  |
| `Case Invitation Evaluated Contact Attempted` | Firm attempted to contact the client     |
| `Case Invitation Evaluated Consult Complete`  | Consultation completed                   |
| `Case Invitation Closed`                      | An invitation is closed                  |

### Update Events

| Event              | Triggered When                              |
| ------------------ | ------------------------------------------- |
| `Case Update Made` | A status update or note is posted to a case |

### Relay Events

| Event                 | Triggered When                          |
| --------------------- | --------------------------------------- |
| `Case Relay Matched`  | Relay Engine finds a matching partner   |
| `Case Relay Rejected` | Relay Engine can't find any matches     |
| `Case Relay Missed`   | Partners were invited but none accepted |
| `Case Relay Stopped`  | Relay matching process was stopped      |

### Other Events

| Event                | Triggered When                             |
| -------------------- | ------------------------------------------ |
| `Case Settlement`    | Settlement process begins on a case        |
| `Case File Uploaded` | A file is uploaded to a case               |
| `Case Missing Info`  | A case is flagged as missing required info |

***

## 📋 Common Use Cases

### 📗 Use Case: Subscribing to Case Creation Events

|            |                                                            |
| ---------- | ---------------------------------------------------------- |
| **Goal**   | Get notified when new cases are created in Lexamica        |
| **How**    | Create a webhook subscription for the `Case Created` event |
| **Result** | Receive POST requests whenever a case is created           |

**Request:**

```bash
curl -X POST \
  "https://integration.lexamica.com/organization/{orgId}/webhook-subscriptions/create" \
  -H "Authorization: Bearer your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "Case Created",
    "url": "https://your-system.com/webhooks/lexamica/case-created",
    "secret": "your_webhook_secret_key",
    "mapping": "64f1a2b3c4d5e6f7a8b9c0d2",
    "description": "Notify CRM of new cases",
    "active": true
  }'
```

**Response:**

```json
{
  "_id": "64f1a2b3c4d5e6f7a8b9c0d5",
  "event": "Case Created",
  "url": "https://your-system.com/webhooks/lexamica/case-created",
  "active": true,
  "description": "Notify CRM of new cases",
  "mapping": {
    "_id": "64f1a2b3c4d5e6f7a8b9c0d2",
    "label": "CRM Case Mapping"
  },
  "created": "2026-01-15T10:30:00.000Z"
}
```

### 📗 Use Case: Verifying Webhook Signatures

|            |                                                        |
| ---------- | ------------------------------------------------------ |
| **Goal**   | Ensure webhooks actually came from Lexamica            |
| **How**    | Verify the HMAC-SHA256 signature in the request header |
| **Result** | Reject any requests that don't match                   |

**Sample webhook payload your endpoint receives:**

Headers:

```
Content-Type: application/json
X-Lexamica-Signature: sha256=a1b2c3d4e5f6...
X-Lexamica-Event: Case Created
```

Body (in your field format):

```json
{
  "case_id": "64f1a2b3c4d5e6f7a8b9c0d3",
  "contact_first_name": "Jane",
  "contact_last_name": "Smith",
  "practice_area": "Personal Injury",
  "created_at": "2026-01-15T10:30:00.000Z"
}
```

**Signature verification (Node.js):**

```javascript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// In your webhook handler:
app.post('/webhooks/lexamica/case-created', (req, res) => {
  const signature = req.headers['x-lexamica-signature'];
  
  if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  // Respond quickly
  res.status(200).send('OK');
  
  // Process asynchronously
  processWebhook(req.body);
});
```

**Signature verification (Python):**

```python
import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(signature, expected)
```

### 📗 Use Case: Testing Your Webhook Endpoint

|            |                                                                |
| ---------- | -------------------------------------------------------------- |
| **Goal**   | Verify your endpoint receives and processes webhooks correctly |
| **How**    | Use the test endpoint to send a sample payload                 |
| **Result** | Confirm your endpoint responds properly                        |

**Request:**

```bash
curl -X POST \
  "https://integration.lexamica.com/organization/{orgId}/webhook-subscriptions/{subscriptionId}/test" \
  -H "Authorization: Bearer your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "case_id": "test_123",
    "contact_first_name": "Test",
    "contact_last_name": "User"
  }'
```

### 📕 Use Case: Handling Failed Deliveries

|            |                                                           |
| ---------- | --------------------------------------------------------- |
| **Goal**   | Understand what happens when your endpoint is unavailable |
| **How**    | Lexamica retries with exponential backoff                 |
| **Result** | You have time to recover; events aren't lost immediately  |

**Retry schedule:**

1. **Immediate** — First attempt
2. **+30 seconds** — Second attempt
3. **+2 minutes** — Third attempt
4. **+5 minutes** — Fourth attempt
5. **+15 minutes** — Fifth attempt
6. **Marked failed** — Logged for review

> **💡 Tip:** If you expect extended downtime, consider using [Event Storage and Polling](/5.-event-storage-polling.md) as a backup.

***

## ❓ FAQ

### ❓ "How do I test my webhook endpoint?"

**🔍 Quick Check:**

* ✅ Use the `/webhook-subscriptions/{subscriptionId}/test` endpoint
* ✅ Or use tools like ngrok to expose a local endpoint

**📝 Full Answer:** The test endpoint lets you trigger your subscription with custom data:

```bash
curl -X POST \
  "https://integration.lexamica.com/organization/{orgId}/webhook-subscriptions/{subscriptionId}/test" \
  -H "Authorization: Bearer your_private_key" \
  -H "Content-Type: application/json" \
  -d '{ "test_field": "test_value" }'
```

This sends a properly signed webhook to your endpoint with the payload you provide.

***

### ❓ "What if my server is down?"

**📝 Full Answer:** Lexamica retries failed deliveries with exponential backoff (up to 5 attempts over \~22 minutes). If all attempts fail, the delivery is marked as failed and logged.

For critical integrations, consider:

1. **Redundant endpoints** — Multiple webhook URLs for the same event
2. **Event storage** — Use stored event subscriptions as a backup to poll for missed events
3. **Monitoring** — Check your logs regularly for failed deliveries

***

### ❓ "Can I receive multiple event types at the same URL?"

**📝 Full Answer:** Yes. Create multiple subscriptions pointing to the same URL, one per event type. Use the `X-Lexamica-Event` header to distinguish between events in your handler:

```javascript
app.post('/webhooks/lexamica', (req, res) => {
  const eventType = req.headers['x-lexamica-event'];
  
  switch (eventType) {
    case 'Case Created':
      handleCaseCreated(req.body);
      break;
    case 'Case Invitation Accepted':
      handleInvitationAccepted(req.body);
      break;
    // ...
  }
  
  res.status(200).send('OK');
});
```

***

### ❓ "How do I pause a subscription temporarily?"

**📝 Full Answer:** Update the subscription and set `active` to `false`:

```bash
curl -X POST \
  "https://integration.lexamica.com/organization/{orgId}/webhook-subscriptions/{subscriptionId}/update" \
  -H "Authorization: Bearer your_private_key" \
  -H "Content-Type: application/json" \
  -d '{ "active": false }'
```

Events that occur while the subscription is inactive won't be delivered. Re-enable it by setting `active` to `true`.

***

### ❓ "Can I add a delay before webhooks are sent?"

**📝 Full Answer:** Yes. When creating or updating a subscription, set the `delay` field (in milliseconds):

```json
{
  "event": "Case Created",
  "url": "https://your-system.com/webhooks",
  "delay": 5000,
  "...": "..."
}
```

This delays delivery by 5 seconds. Useful if you need time for related data to settle before processing.

***

## 🔧 Technical Reference

### Webhook Request Format

Each webhook POST includes:

**Headers:**

| Header                 | Description                            |
| ---------------------- | -------------------------------------- |
| `Content-Type`         | `application/json`                     |
| `X-Lexamica-Signature` | HMAC-SHA256 signature: `sha256=<hash>` |
| `X-Lexamica-Event`     | The event type (e.g., `Case Created`)  |

**Body:** The event payload, transformed via your mapping to use your field names.

### Subscription Fields

| Field         | Type    | Required | Description                                     |
| ------------- | ------- | -------- | ----------------------------------------------- |
| `event`       | String  | Yes      | The event type to subscribe to                  |
| `url`         | String  | Yes      | Your webhook endpoint URL                       |
| `secret`      | String  | Yes      | Key used to sign payloads (HMAC-SHA256)         |
| `mapping`     | String  | Yes      | Mapping ID for transforming payloads            |
| `description` | String  | No       | Human-readable description                      |
| `active`      | Boolean | No       | Enable/disable the subscription (default: true) |
| `delay`       | Number  | No       | Milliseconds to wait before sending             |

### Verify Endpoint

You can verify a webhook signature using the API:

```bash
curl -X POST \
  "https://integration.lexamica.com/organization/{orgId}/webhook-subscriptions/{event}/verify" \
  -H "Authorization: Bearer your_private_key" \
  -H "Content-Type: application/json" \
  -H "X-Lexamica-Signature: sha256=abc123..." \
  -d '{ "original": "payload" }'
```

> **🔧 Technical Deep-Dive: Signature Verification**
>
> The signature is computed as:
>
> ```
> HMAC-SHA256(secret, JSON.stringify(payload))
> ```
>
> Prefixed with `sha256=` in the header. Always use timing-safe comparison to prevent timing attacks.
>
> **Important:** The signature is computed on the exact JSON string sent. If your framework parses and re-serializes the body, the signature may not match. Use the raw request body for verification.

### Event Payload Examples

**Case Created:**

```json
{
  "case_id": "64f1a2b3c4d5e6f7a8b9c0d3",
  "contact_first_name": "Jane",
  "contact_last_name": "Smith",
  "contact_email": "jane@example.com",
  "practice_area": "Personal Injury",
  "incident_date": "2026-01-10",
  "status": "pending"
}
```

**Case Invitation Accepted:**

```json
{
  "invitation_id": "64f1a2b3c4d5e6f7a8b9c0d4",
  "case_id": "64f1a2b3c4d5e6f7a8b9c0d3",
  "handler_firm_id": "64f1a2b3c4d5e6f7a8b9c0d6",
  "handler_firm_name": "Smith & Associates",
  "accepted_at": "2026-01-15T14:22:00.000Z"
}
```

**Case Relay Matched:**

```json
{
  "case_id": "64f1a2b3c4d5e6f7a8b9c0d3",
  "matched_firm_id": "64f1a2b3c4d5e6f7a8b9c0d6",
  "matched_firm_name": "Smith & Associates",
  "invitation_id": "64f1a2b3c4d5e6f7a8b9c0d4"
}
```

***

*Last updated: January 2026*
