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

# How to use Webhooks

Only our **Universal CRM API** mirrors the [Follow Up Boss API](https://followupboss.com/api) in terms of event names and payload structure. Our **TMS** (Transaction Management System) and **LOS** (Loan Origination System) Universal APIs do **not** mirror the Follow Up Boss API, but they do follow the same event structure and behavior for each entity type.

For each CRM entity—such as people, tasks, notes, etc.—we support standard lifecycle events:

* `entity`Created
* `entity`Updated
* `entity`Deleted

For example, to receive notifications when new people are added, you would subscribe to the peopleCreated event.

**Note:** At this time, we do **not** support all granular event types (such as peopleStageUpdated) that exist in Follow Up Boss itself except for the peopleTagsCreated event.

#### **Event Payloads**

Each webhook payload includes:

* eventId: A unique identifier for the event.
* eventCreated: Timestamp for when the event occurred.
* event: The event name (e.g., peopleCreated).
* resourceIds: List of IDs for affected records.
* uri: A direct URL to fetch the relevant records.

**Example peopleCreated payload:**

```json theme={null}
{
  "eventId": "0197cba6-834f-7000-b5ed-1813cd6efcbe",
  "eventCreated": "2025-07-02T15:19:21+00:00",
  "event": "peopleCreated",
  "resourceIds": [
    "0197cbb0-efe2-7000-9cc2-7b029bcf7a1e",
    "0197cbb1-2a8e-7000-b339-674c58902431",
    "0197cbb1-5816-7000-a3a4-90be8a6057c4"
  ],
  "uri": "https://crm.universal.rollout.com/people?ids=0197cbb0-efe2-7000-9cc2-7b029bcf7a1e,0197cbb1-2a8e-7000-b339-674c58902431,0197cbb1-5816-7000-a3a4-90be8a6057c4"
}
```

#### Delivery Semantics and Retries

* **Delivery guarantee:** at-least-once (not exactly-once).
* **Retry behavior:** Rollout retries with backoff until your endpoint returns a `2xx` status.
* **Duplicates can happen:** for example, if your endpoint processed the event but the response timed out before we received the `2xx`.
* **Best practice:** treat webhooks as idempotent and deduplicate by `eventId`.

#### **Credential Sync Completion Callback**

If you configure callback URLs in Dashboard Settings, Rollout sends a callback when a credential's initial sync is completed and Sync to DB is ready.

* **Method:** `POST`
* **When sent:** after initial sync completes for the credential
* **Security:** same signature verification method as all webhooks (see [Security & Authenticity](#security-authenticity))

**Where to configure it:**

<img src="https://mintcdn.com/rollout/J3SgtcePtApyhjtP/images/callback-settings.png?fit=max&auto=format&n=J3SgtcePtApyhjtP&q=85&s=045dd9725e9b23a2cd2b8242fa302199" alt="Callback URL settings in Dashboard" width="2062" height="680" data-path="images/callback-settings.png" />

**Payload shape:**

```ts theme={null}
type SyncCompletedCallbackPayload = {
  credential: {
    id: string;
    appKey: string;
  };
  consumers: {
    id: string;
    consumerKey: string;
  }[];
};
```

**Example payload:**

```json theme={null}
{
  "credential": {
    "id": "0197f65e-0636-7000-b7f6-3504519f57f0",
    "appKey": "followupboss"
  },
  "consumers": [
    {
      "id": "cons_01j1r1xaxz9b23v0j8f2qgkcpd",
      "consumerKey": "sync-to-db"
    }
  ]
}
```

#### **Security & Authenticity**

To ensure webhook requests are genuinely from us, we include an `X-Rollout-Signature` header. This contains an HMAC SHA256 signature generated using your **Rollout Client Secret** as the key.

Rollout signs the base64 encoding of the exact JSON request body string we send. There is no additional prefix, timestamp, or canonicalization step.

To verify a webhook:

1. Read the raw request body bytes before parsing JSON.
2. Base64-encode those bytes.
3. Compute an HMAC SHA256 hash of that base64 string using your client secret.
4. Compare your hash to the value in the `X-Rollout-Signature` header using a constant-time comparison.

```ts theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyRolloutSignature(
  rawBody: Buffer,
  signature: string | undefined,
  clientSecret: string,
) {
  if (signature == null) {
    return false;
  }

  const expected = createHmac("sha256", clientSecret)
    .update(rawBody.toString("base64"))
    .digest("hex");

  const expectedBuffer = Buffer.from(expected, "hex");
  const signatureBuffer = Buffer.from(signature, "hex");

  return (
    signatureBuffer.length === expectedBuffer.length &&
    timingSafeEqual(expectedBuffer, signatureBuffer)
  );
}
```
