> ## Documentation Index
> Fetch the complete documentation index at: https://docs.leadscoutapp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# REST API endpoints for registering outbound webhooks

> Register outbound webhooks to receive real-time event notifications. Configure event filters, delivery delays, and trigger modes. Requires admin role.

Webhooks let you push LeadScout events to your own servers in real time. When a configured event occurs — such as a prospect's status changing or a new appointment being created — LeadScout sends an HTTP POST to your registered URL with a signed JSON payload. You can register multiple webhooks, each with its own URL, event subscriptions, optional filters, and delivery settings.

<Warning>
  All webhook endpoints require the `admin` role or higher. Requests from `sales` role users return `403`.
</Warning>

## List webhooks

```
GET /api/webhooks
```

Returns all active webhooks for your company, including their event registrations, ordered newest first.

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url https://app.leadscoutapp.com/api/webhooks \
    --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://app.leadscoutapp.com/api/webhooks', {
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  const webhooks = await response.json();
  ```
</CodeGroup>

### Response

Returns an array of webhook objects.

<ResponseField name="id" type="number">Webhook ID.</ResponseField>
<ResponseField name="name" type="string">Display name for this webhook.</ResponseField>
<ResponseField name="url" type="string">The endpoint URL that receives POST requests.</ResponseField>
<ResponseField name="secretKey" type="string">HMAC signing secret used to verify delivery. See [Verifying signatures](#verifying-signatures).</ResponseField>
<ResponseField name="companyId" type="number">Your company ID.</ResponseField>
<ResponseField name="createdAt" type="string">ISO 8601 creation timestamp.</ResponseField>

<ResponseField name="registrations" type="object[]">
  Active event subscriptions for this webhook.

  <Expandable title="registration object">
    <ResponseField name="id" type="number">Registration ID.</ResponseField>
    <ResponseField name="event" type="string">Event name (e.g. `prospect.status_changed`).</ResponseField>
    <ResponseField name="filter" type="object | null">Optional filter criteria applied before delivery.</ResponseField>
    <ResponseField name="delayMinutes" type="number">Delivery delay in minutes (default `0`).</ResponseField>
    <ResponseField name="triggerMode" type="string">`automatic` or `manual`.</ResponseField>
    <ResponseField name="manualTriggerButtonText" type="string | null">Label shown on the manual trigger button, if `triggerMode` is `manual`.</ResponseField>
  </Expandable>
</ResponseField>

***

## Get a webhook

```
GET /api/webhooks/:id
```

Returns a single webhook with its registrations. Returns `404` if it does not exist or belongs to a different company.

### Path parameters

<ParamField path="id" type="number" required>
  The webhook ID.
</ParamField>

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url https://app.leadscoutapp.com/api/webhooks/8 \
    --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://app.leadscoutapp.com/api/webhooks/8', {
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  const webhook = await response.json();
  ```
</CodeGroup>

***

## Create a webhook

```
POST /api/webhooks
```

### Request body

<ParamField body="name" type="string" required>
  A human-readable name to identify this webhook. Must be at least 1 character.
</ParamField>

<ParamField body="url" type="string" required>
  The HTTPS URL that will receive POST payloads. Must be a valid URL.
</ParamField>

<ParamField body="secretKey" type="string">
  HMAC signing secret. If you omit this, a UUID is generated automatically and returned in the response. Store it — it is not shown again.
</ParamField>

Returns `201` with the created webhook object on success. The `secretKey` is included in the response body only at creation time.

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://app.leadscoutapp.com/api/webhooks \
    --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
    --header 'Content-Type: application/json' \
    --data '{
      "name": "CRM sync",
      "url": "https://hooks.yourcrm.com/leadscout"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://app.leadscoutapp.com/api/webhooks', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: 'CRM sync',
      url: 'https://hooks.yourcrm.com/leadscout',
    }),
  });
  const webhook = await response.json();
  // Store webhook.secretKey now — it is not surfaced again
  ```
</CodeGroup>

***

## Update a webhook

```
PATCH /api/webhooks/:id
```

All body fields are optional. Only the fields you send are updated.

### Path parameters

<ParamField path="id" type="number" required>
  The webhook ID.
</ParamField>

### Request body

<ParamField body="name" type="string">New display name. Must be at least 1 character.</ParamField>
<ParamField body="url" type="string">New destination URL. Must be a valid URL.</ParamField>

<CodeGroup>
  ```bash cURL theme={null}
  curl --request PATCH \
    --url https://app.leadscoutapp.com/api/webhooks/8 \
    --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
    --header 'Content-Type: application/json' \
    --data '{ "url": "https://hooks.yourcrm.com/leadscout-v2" }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://app.leadscoutapp.com/api/webhooks/8', {
    method: 'PATCH',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ url: 'https://hooks.yourcrm.com/leadscout-v2' }),
  });
  const updated = await response.json();
  ```
</CodeGroup>

***

## Delete a webhook

```
DELETE /api/webhooks/:id
```

Deletes the webhook. No further deliveries are made after deletion.

### Path parameters

<ParamField path="id" type="number" required>
  The webhook ID.
</ParamField>

Returns `{ "id": <number> }` on success.

<CodeGroup>
  ```bash cURL theme={null}
  curl --request DELETE \
    --url https://app.leadscoutapp.com/api/webhooks/8 \
    --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
  ```

  ```javascript JavaScript theme={null}
  await fetch('https://app.leadscoutapp.com/api/webhooks/8', {
    method: 'DELETE',
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  ```
</CodeGroup>

***

## Event registrations

Each webhook can subscribe to one or more events through registrations. Registrations let you fine-tune which events trigger delivery, add optional filters, delay delivery, and choose between automatic and manual trigger modes.

### Registration fields

<ParamField body="event" type="string" required>
  The event name to subscribe to. Must be at least 1 character.
</ParamField>

<ParamField body="filter" type="object">
  Key/value criteria that the event payload must match before delivery is triggered. Pass `null` to receive all events of this type.
</ParamField>

<ParamField body="delayMinutes" type="number">
  Integer number of minutes to wait before delivering the event. Minimum `0` (immediate). Use delays to batch or debounce rapid status changes.
</ParamField>

<ParamField body="triggerMode" type="string">
  `automatic` — delivered automatically when the event fires. `manual` — a button appears in the LeadScout UI for a rep to trigger the delivery. Defaults to `automatic`.
</ParamField>

<ParamField body="manualTriggerButtonText" type="string">
  Label for the manual trigger button. Only used when `triggerMode` is `manual`.
</ParamField>

***

## Verifying signatures

LeadScout signs every delivery with an HMAC-SHA256 signature computed from the raw request body and your webhook's `secretKey`. Verify the signature on your server before processing the payload to confirm the request originated from LeadScout.

<Steps>
  <Step title="Read the signature header">
    The signature is sent in the `X-LeadScout-Signature` header as a hex-encoded HMAC-SHA256 digest.
  </Step>

  <Step title="Compute the expected signature">
    Compute `HMAC-SHA256(secretKey, rawBody)` on your server using the `secretKey` you received when the webhook was created.
  </Step>

  <Step title="Compare signatures">
    Use a constant-time comparison to compare your computed signature against the header value. Reject the request if they do not match.
  </Step>
</Steps>

```javascript JavaScript theme={null}
import { createHmac, timingSafeEqual } from 'crypto';

function verifySignature(rawBody, secretKey, signatureHeader) {
  const expected = createHmac('sha256', secretKey)
    .update(rawBody)
    .digest('hex');
  const actual = Buffer.from(signatureHeader, 'hex');
  const expectedBuf = Buffer.from(expected, 'hex');
  return actual.length === expectedBuf.length &&
    timingSafeEqual(actual, expectedBuf);
}
```

<Warning>
  Always use a constant-time comparison (`timingSafeEqual`) rather than `===` to prevent timing attacks.
</Warning>
