# Webhooks

Subscribe to real-time notifications when contacts change jobs or companies experience key business events.

Webhooks deliver HTTP POST requests to your endpoints when signals occur - from promotions and job changes to company growth.

> For a full list of available signals, refer to the **Signal Options** endpoint under the Signals section.
---
**Key Features:**
- Real-time contact & company signal notifications
- Bulk subscription management (up to 25 items per request)
- Secure delivery with HMAC-SHA256 signatures
- Delivery monitoring with audit logs

 **Available Endpoints:**

| Method | Endpoint | Purpose |
|--------|----------|---------|
| POST | `/api/subscriptions` | Create subscriptions (bulk supported) |
| GET | `/api/subscriptions` | List all subscriptions |
| GET | `/api/subscriptions/{id}` | Get subscription by ID |
| PATCH | `/api/subscriptions/{id}` | Update subscription |
| POST | `/api/subscriptions/delete` | Delete subscriptions (bulk supported) |
| POST | `/api/subscriptions/{id}/test` | Test subscription delivery |
| GET | `/api/audit-logs` | Get webhook delivery logs |
| GET | `/api/audit-logs/stats` | Get delivery statistics |
| GET | `/api/account/secret` | Get account webhook secret |
| POST | `/api/account/secret/regenerate` | Regenerate account secret |

> **Webhook Delivery Acknowledgment:** When receiving webhook deliveries (POST requests), your endpoint must acknowledge with a specific response format. See the Create Subscription endpoint for the required acknowledgment structure.
      ---

<details>
<summary><strong>Rate Limits</strong></summary>

| Operation | Limit |
|-----------|-------|
| API Requests | 100 requests/minute per account |
| Create Subscriptions | 25 items per request |
| Delete Subscriptions | 25 items per request |

</details>

---

<details>
<summary><strong>Security & Verification</strong></summary>

**HTTPS Requirement:**
- Production webhook URLs **must** use HTTPS
- HTTP URLs are not accepted

**Signature Verification:**

All webhook deliveries include an `X-Lusha-Signature` header containing an HMAC-SHA256 signature. Verify this signature to ensure the request is from Lusha:

1. Extract the `X-Lusha-Signature` and `X-Lusha-Timestamp` headers
2. Concatenate: `timestamp + "." + JSON.stringify(payload)`
3. Compute HMAC-SHA256 using your webhook secret
4. Compare the computed signature with the received signature

**Example (Node.js):**
```javascript
const crypto = require('crypto');

function verifySignature(payload, signature, timestamp, secret) {
  const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}
```

> **Security Best Practice:** Always verify webhook signatures to prevent spoofed requests.

</details>

---

<details>
<summary><strong>Credits & Billing</strong></summary>

**Credit Charges:**
- Credits are charged when signals are detected and delivered to your webhook
- The `creditsCharged` field in the webhook payload indicates how many credits were used
- Credits are deducted from your account balance per signal type

**No Duplicate Charges:**
- Each signal is delivered once and charged once
- Webhook delivery retries do not incur additional charges

</details>

---

<details>
<summary><strong>Error Response Format</strong></summary>

All error responses follow this format:
```json
{
  "statusCode": 400,
  "message": "Validation failed",
  "errors": ["entityType must be one of: contact, company"]
}
```

| Field | Type | Description |
|-------|------|-------------|
| `statusCode` | number | HTTP status code |
| `message` | string | Error message |
| `errors` | string[] | Detailed error messages (optional) |

</details>

---


## Create Subscription

 - [POST /api/subscriptions](https://docs.lusha.com/apis/v2/openapi/webhooks/createsubscription.md): Creates one or more webhook subscriptions for real-time signal notifications.

Delivery & Reliability:
- Webhooks are delivered with automatic retry on failures
- Maximum 3 retry attempts with exponential backoff
- Subscriptions auto-disable after max retries exceeded
- All deliveries are logged in audit logs

> Note: Your webhook endpoint must respond with a proper acknowledgment.
 See Client Response Format below for details.

> Limit: Maximum 25 subscriptions per request

Endpoint: (POST) https://api.lusha.com/api/subscriptions

---

### Webhook Payload You'll Receive
 When a signal is triggered, this payload is sent to your webhook URL:
json
    {
      "id": "f3b87e05-0402-4f3e-8e26-6a38fd0ad62c",
      "type": "promotion",
      "entityType": "contact",
      "entityId": "4158887495",
      "subscriptionId": "507f1f77bcf86cd799439011",
      "data": {
        "personId": 4158887495,
        "currentCompanyId": 40823133,
        "currentCompanyName": "OMG Hospitality Group LLC",
        "currentDomain": "omghospitalitygroup.com",
        "currentTitle": "Bartender",
        "currentDepartments": [
          { "id": 7, "value": "Other" }
        ],
        "previousCompanyName": "First Watch Restaurants",
        "previousDomain": "firstwatch.com",
        "signalDate": "2025-07-01"
      },
      "timestamp": "2026-01-14T16:16:35.841Z",
      "billing": {
        "creditsCharged": 1
      }
    }
    
            Example — Company News Signal:
    json
            {
              "id": "a7c92f14-1234-4b3e-9d22-8b4fe1d0bc45",
              "type": "commercialActivityNews",
              "entityType": "company",
              "entityId": "33222678",
              "subscriptionId": "507f1f77bcf86cd799439011",
              "data": {
                "companyId": "33222678",
                "companyName": "Lusha",
                "domain": "lusha.com",
                "signalId": "1503910",
                "eventType": "partnership",
                "eventSummary": "Lusha announced a strategic partnership with Salesforce.",
                "articlePublishedDate": "2025-06-15",
                "articleTitle": "Lusha Partners with Salesforce",
                "articleHighlight": "The partnership enables Salesforce users to access Lusha data directly within their CRM.",
                "eventEffectiveDate": "2025-06-10",
                "articleUrl": "https://example.com/lusha-salesforce-partnership"
              },
              "timestamp": "2026-01-14T16:16:35.841Z",
              "billing": {
                "creditsCharged": 1
              }
            }
          

      Headers Included:

      | Header | Description |
      |--------|-------------|
      | X-Lusha-Signature | HMAC-SHA256 signature for verification |
      | X-Lusha-Timestamp | Unix timestamp of the request |
      | Content-Type | application/json |
      | User-Agent | Lusha-Webhooks/1.0 |


---
⚠️ Important: Ensure your account has a webhook secret before creating subscriptions.
Create one via the Regenerate Account Secret endpoint.

---

### Client Response Format (Required)

When your webhook endpoint receives a delivery, it must acknowledge receipt with this response:

  Required Response:
  json
  {
    "received": true,
    "timestamp": "2026-02-05T10:30:45.123Z",
    "webhookId": "f3b87e05-0402-4f3e-8e26-6a38fd0ad62c"
  }
  


Response Requirements

  | Requirement | Value |
  |-------------|-------|
  | HTTP Status | 201 Created (recommended) or any 2xx status |
  | Content-Type | application/json |
  | Response Time | Within 10 seconds |




Field Descriptions & Implementation Guide

  Field Descriptions:
  * received (boolean, required): Confirmation flag - must be true
  * timestamp (string, required): ISO 8601 timestamp of receipt
  * webhookId (string, required): Echo the id from webhook payload

  Implementation Example:
  javascript
  app.post('/webhook', async (req, res) => {
    // 1. Verify signature
    if (!verifyWebhookSignature(req)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    // 2. Queue for async processing
    await queueWebhook(req.body);

    // 3. Acknowledge immediately
    res.status(201).json({
      received: true,
      timestamp: new Date().toISOString(),
      webhookId: req.body.id
    });
    

  Important Notes:
  * Return acknowledgment before heavy processing
  * Non-2xx responses trigger retry mechanism
  * After 3 failed retries, subscription is disabled

  

----

## List Subscriptions

 - [GET /api/subscriptions](https://docs.lusha.com/apis/v2/openapi/webhooks/listsubscriptions.md): Returns all webhook subscriptions for your account with pagination support.

Endpoint: (GET) https://api.lusha.com/api/subscriptions

Pagination:
- Results are sorted by createdAt in descending order (newest first)
- Default limit: 10, max limit: 100
- Use offset for pagination through large result sets

> Note: The webhook secret is never returned in list responses for security.

## Get Subscription by ID

 - [GET /api/subscriptions/{id}](https://docs.lusha.com/apis/v2/openapi/webhooks/getsubscriptionbyid.md): Returns a single webhook subscription by ID.

Endpoint: (GET) https://api.lusha.com/api/subscriptions/{id}

## Update Subscription

 - [PATCH /api/subscriptions/{id}](https://docs.lusha.com/apis/v2/openapi/webhooks/updatesubscription.md): Updates an existing webhook subscription. All fields are optional.

Endpoint: (PATCH) https://api.lusha.com/api/subscriptions/{id}

---
Reactivating Disabled Subscriptions:

When setting isActive: true on a previously disabled subscription, the system automatically:
- Clears the blockReason field
- Clears the blockedAt timestamp
- Resets the retry counter

Regenerating Secrets:

Set regenerateSecret: true to generate a new webhook secret. The new secret:
- Affects all subscriptions for your account
- Is only shown once in the response
- Immediately invalidates the old secret
---

## Test Subscription

 - [POST /api/subscriptions/{id}/test](https://docs.lusha.com/apis/v2/openapi/webhooks/testsubscription.md): Test a webhook subscription by sending a test signal. Supports three test modes.

Endpoint: (POST) https://api.lusha.com/api/subscriptions/{id}/test
---
Test Modes:
- direct - Quick HTTP check only (validates URL responds correctly)
- kafka - Fanout handler only (tests Kafka message processing)
- full - Complete Kafka flow (default - end-to-end test)

What Gets Tested:
- URL accessibility and response time
- Complete delivery pipeline (for full mode)
- Signature generation and headers

Important Notes:
- Test deliveries do NOT consume credits
- Test payloads use mock data
- Useful for verifying webhook configuration before going live

## Delete Subscriptions

 - [POST /api/subscriptions/delete](https://docs.lusha.com/apis/v2/openapi/webhooks/deletesubscriptions.md): Delete one or more webhook subscriptions. Returns detailed results for each deletion with partial success support.

Endpoint: (POST) https://api.lusha.com/api/subscriptions/delete

---

Behavior:
- Each subscription is processed independently
- Returns detailed results for each item including deleted subscription info
- Invalid ID formats are gracefully handled and reported as NOT_FOUND
- Duplicate IDs are automatically deduplicated
- Deletion is permanent and cannot be undone

## Get Audit Logs

 - [GET /api/audit-logs](https://docs.lusha.com/apis/v2/openapi/webhooks/getauditlogs.md): Retrieve webhook delivery logs for your account.

Endpoint: (GET) https://api.lusha.com/api/audit-logs

What's Logged:
- All webhook delivery attempts (success and failures)
- HTTP status codes and response times
- Error messages for failed deliveries
- Delivery timestamps and duration metrics

Filtering:
- Filter by subscription ID to see logs for specific subscriptions
- Filter by status to see only successes, failures, or permanent failures

Rate Limit: 100 requests/minute per account

> Note: Logs are retained for 90 days (successful) and 180 days (failed/DLQ)

## Get Audit Log Statistics

 - [GET /api/audit-logs/stats](https://docs.lusha.com/apis/v2/openapi/webhooks/getauditlogstats.md): Get delivery statistics for your account.

Endpoint: (GET) https://api.lusha.com/api/audit-logs/stats

## Get Account Secret

 - [GET /api/account/secret](https://docs.lusha.com/apis/v2/openapi/webhooks/getaccountsecret.md): Retrieve the current account webhook secret.

Endpoint: (GET) https://api.lusha.com/api/account/secret

## Regenerate Account Secret

 - [POST /api/account/secret/regenerate](https://docs.lusha.com/apis/v2/openapi/webhooks/regenerateaccountsecret.md): Regenerate the account webhook secret. Affects all subscriptions for the account.

Endpoint: (POST) https://api.lusha.com/api/account/secret/regenerate

Behavior:
- If a secret already exists: Replaces with new secret (old secret is invalidated)
- If no secret exists: Creates new secret automatically

Important Notes:
- The secret is only shown once in the response. Store it securely.
- This endpoint always succeeds (upsert operation)
- Regenerating invalidates the old secret for all subscriptions (if one existed)
- An account secret must exist before webhooks can be delivered

