# Webhooks: event catalog

Source: https://help.ruuster.com/api/webhook-events

> Every lead event kind, the event envelope, the objects inside it, and example payloads.

Events are sent for known leads only, from the time your brokerage was enabled. Email open and click events cover emails sent in the previous three days; later opens still count in the statistics. Anonymous visitors produce no events; their views show up in
listing totals.

| Kind | Fires when | `data` |
|---|---|---|
| `lead.created` | A new lead appears: a registration or a contact synced from your CRM | `lead`, `leadType`, `createdBy` |
| `lead.property_view` | A lead opens a listing page (once per lead, listing and day, with `viewsToday`) | `lead`, `property`, `viewsToday` |
| `lead.favorite` | A lead saves a listing | `lead`, `property` |
| `lead.saved_search_created` | A saved search is created for the lead, by the lead or an agent | `lead`, `savedSearch`, `createdBy` |
| `lead.saved_search_updated` | A lead's saved search changes | `lead`, `savedSearch`, `updatedBy` |
| `lead.search` | A lead runs a listings or rentals search | `lead`, `search.type` |
| `lead.email_open` | First human open of a Ruuster email | `lead`, `email` |
| `lead.email_click` | First human click in a Ruuster email | `lead`, `email` |
| `lead.tour_request` | A lead requests a tour | `lead`, `property`, `tourDate`, `partOfDay` |
| `lead.contact_agent` | A lead sends the contact-agent form | `lead`, `property` (if sent from a listing), `agentId` |
| `lead.login` | A lead signs in | `lead`, `platform` (`web`, `ios`, `android` when known) |
| `lead.share` | A lead shares a listing | `lead`, `property`, `channel` |
| `lead.email_status_changed` | Ruuster's verdict on the lead's email address changes: a check or a send fails, the lead unsubscribes, delivery is turned back on, or a new lead's address gets its first verdict | `lead`, `emailStatus` |
| `webhook.test` | You call the test endpoint | `message`, `subscriptionId` |

Subscribe to kinds one by one, or to `lead.*` for all of them, including kinds added later.
`lead.search` and `lead.property_view` are high volume.

**Envelope**

| Field | Description |
|---|---|
| `id` | Event id, `evt_…`. De-duplicate on this |
| `kind` | One of the kinds above |
| `occurredAt` | When it happened in Ruuster |
| `brokerageId` | Whose event it is |
| `apiVersion` | `v1` |
| `data` | Kind-specific, see above |
| `links` | API URLs for the lead and listing involved |

**Objects inside `data`**

| Object | Fields |
|---|---|
| `lead` | `leadId`, `firstName`, `lastName`, `crmExternalId`, `agentId`; `email` and `phone` when lead contact details are on |
| `property` | `propertyId`, `mlsRecordId`, `mlsName`, `address`, `ownedByYou`, for any listing a lead engaged with, yours (`ownedByYou: true`) or another brokerage's (`false`). `links.property` points at the listing's engagement for yours and at [Get a listing](https://help.ruuster.com/api/properties-get) otherwise. A listing that has since left the feed carries `propertyId` and `ownedByYou` only |
| `savedSearch` | `savedSearchId`, `name` |
| `email` | `emailId`, `subject`, `savedSearchName`, `sentAt` |
| `createdBy`, `updatedBy` | `{ "type": "lead" \| "agent", "id": … }` |
| `emailStatus` | `status` (`valid`, `invalid`, `blocked`, `soft_bounce`, `complaint`, `unsubscribed`, `unknown`), `code`, `reason`, `source`, `failedAt`, `since`, `previousStatus`. See the [Email status](https://help.ruuster.com/api/email-status) guide for what each status means |

## Example deliveries

Every delivery is a `POST` with these headers, then one of the bodies below:

```
POST /ruuster HTTP/1.1
Host: hooks.saguarorealty.com
Content-Type: application/json
User-Agent: Ruuster-Webhooks/1.0
Ruuster-Event-Id: evt_9a17f2c4b8e05d31
Ruuster-Event-Kind: lead.tour_request
Ruuster-Delivery-Id: dlv_4d7b2e90c1
Ruuster-Signature: t=1790869330,v1=6b0f3c2e8a91d4f7b5c0e2a9d8f61b3c7e4a2d0f9b8c6e1a3d5f7b9c2e4a6d8f
```

Common envelope (shown in full once; the examples after it show only `kind`, `occurredAt` and `data`):

```json
{
  "id": "evt_9a17f2c4b8e05d31",
  "kind": "lead.tour_request",
  "occurredAt": "2026-09-30T02:19:03Z",
  "brokerageId": "4f6c2a9e-8d31-4b57-9a0e-2c7d5e1b8f43",
  "apiVersion": "v1",
  "data": {
    "lead": { "leadId": "user:c0ff3e21-8a4b-4c7d-9e15-2b6f8d3a1c04", "firstName": "Sam", "lastName": "Lee",
              "email": "sam.lee@example.com", "phone": "+15205550142", "crmExternalId": "48213",
              "agentId": "7c1e9b42-3d85-4a6f-9e07-1b2c8d4f5a69" },
    "property": { "propertyId": "5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86", "mlsRecordId": "22418765",
                  "address": "4182 E Saguaro Bloom Dr, Tucson, AZ 85718", "ownedByYou": true },
    "tourDate": "2026-10-04",
    "partOfDay": "morning"
  },
  "links": {
    "lead": "https://api.ruuster.com/v1/leads/user:c0ff3e21-8a4b-4c7d-9e15-2b6f8d3a1c04/activity",
    "property": "https://api.ruuster.com/v1/properties/5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86/engagement"
  }
}
```

Below, `LEAD_SAM` stands for the `lead` object above, `LEAD_PRIYA` for
`{ "leadId": "unregistered:8d2b6f19-4e37-4a85-b0c9-7f1e3d5a2b68", "firstName": "Priya", "lastName": "Shah", "email": "priya.shah@example.com", "phone": "+15205550187", "crmExternalId": "51907", "agentId": "e5a7d3c1-9b46-4f28-8c0e-6d2a1f7b3e95" }`,
and `PROP_A` for the `property` object above. In real deliveries these are always written out in full.

**Lead events**

```json
{ "kind": "lead.created", "occurredAt": "2026-09-23T15:44:12Z",
  "data": { "lead": LEAD_PRIYA, "leadType": "unregistered", "createdBy": { "type": "agent", "id": "e5a7d3c1-9b46-4f28-8c0e-6d2a1f7b3e95" } } }
```

```json
{ "kind": "lead.property_view", "occurredAt": "2026-09-27T23:40:12Z",
  "data": { "lead": LEAD_SAM, "property": PROP_A, "viewsToday": 3 } }
```

```json
{ "kind": "lead.favorite", "occurredAt": "2026-09-29T14:21:55Z",
  "data": { "lead": LEAD_SAM, "property": PROP_A } }
```

```json
{ "kind": "lead.saved_search_created", "occurredAt": "2026-09-29T14:30:02Z",
  "data": { "lead": LEAD_SAM, "savedSearch": { "savedSearchId": "b81f4d20-6c3e-4a97-9e15-0d7c2a5f8b36", "name": "Catalina Foothills 3bd" },
            "createdBy": { "type": "lead", "id": "user:c0ff3e21-8a4b-4c7d-9e15-2b6f8d3a1c04" } } }
```

```json
{ "kind": "lead.saved_search_updated", "occurredAt": "2026-09-30T16:05:41Z",
  "data": { "lead": LEAD_SAM, "savedSearch": { "savedSearchId": "b81f4d20-6c3e-4a97-9e15-0d7c2a5f8b36", "name": "Catalina Foothills 3bd" },
            "updatedBy": { "type": "agent", "id": "7c1e9b42-3d85-4a6f-9e07-1b2c8d4f5a69" } } }
```

```json
{ "kind": "lead.search", "occurredAt": "2026-09-28T13:04:27Z",
  "data": { "lead": LEAD_SAM, "search": { "type": "listings" } } }
```

```json
{ "kind": "lead.email_open", "occurredAt": "2026-09-29T14:12:02Z",
  "data": { "lead": LEAD_SAM,
            "email": { "emailId": "em_7Yq2pX8mT4cW", "subject": "3 new homes match \"Catalina Foothills 3bd\"", "savedSearchName": "Catalina Foothills 3bd", "sentAt": "2026-09-29T12:00:31Z" } } }
```

```json
{ "kind": "lead.email_click", "occurredAt": "2026-09-29T14:15:40Z",
  "data": { "lead": LEAD_SAM,
            "email": { "emailId": "em_7Yq2pX8mT4cW", "subject": "3 new homes match \"Catalina Foothills 3bd\"", "savedSearchName": "Catalina Foothills 3bd", "sentAt": "2026-09-29T12:00:31Z" } } }
```

```json
{ "kind": "lead.tour_request", "occurredAt": "2026-09-30T02:19:03Z",
  "data": { "lead": LEAD_SAM, "property": PROP_A, "tourDate": "2026-10-04", "partOfDay": "morning" } }
```

```json
{ "kind": "lead.contact_agent", "occurredAt": "2026-09-30T19:07:56Z",
  "data": { "lead": LEAD_PRIYA,
            "property": { "propertyId": "a03c8f61-7e2b-4d95-8a14-6f0b3e9c2d57", "mlsRecordId": "22419302", "address": "1920 W Mesquite Ridge Ln, Oro Valley, AZ 85737", "ownedByYou": true },
            "agentId": "e5a7d3c1-9b46-4f28-8c0e-6d2a1f7b3e95" } }
```

```json
{ "kind": "lead.login", "occurredAt": "2026-09-30T02:11:40Z",
  "data": { "lead": LEAD_SAM, "platform": "ios" } }
```

```json
{ "kind": "lead.share", "occurredAt": "2026-09-30T23:58:44Z",
  "data": { "lead": LEAD_SAM, "property": PROP_A, "channel": "sms" } }
```

```json
{ "kind": "lead.email_status_changed", "occurredAt": "2026-10-29T14:10:00Z",
  "data": { "lead": LEAD_SAM,
            "emailStatus": { "status": "invalid", "code": "5.1.1 bounce",
                             "reason": "The mailbox does not exist or no longer accepts mail.",
                             "source": "delivery", "failedAt": "2026-10-29T14:02:11Z",
                             "since": "2026-10-29T14:10:00Z", "previousStatus": "valid" } } }
```

**Test event**

```json
{ "id": "evt_test_1b6d0c93", "kind": "webhook.test", "occurredAt": "2026-10-01T14:21:30Z",
  "brokerageId": "4f6c2a9e-8d31-4b57-9a0e-2c7d5e1b8f43", "apiVersion": "v1",
  "data": { "message": "Hello from Ruuster", "subscriptionId": "whk_2c7f9a4e1b" } }
```

**Batch** (sent when a subscription would receive more than one event per second):

```json
{
  "id": "bat_0e4c7a91d2",
  "kind": "batch",
  "apiVersion": "v1",
  "count": 2,
  "events": [
    { "id": "evt_71c2e9b04d3a8f65", "kind": "lead.property_view", "occurredAt": "2026-09-28T13:05:02Z", "brokerageId": "4f6c2a9e-8d31-4b57-9a0e-2c7d5e1b8f43", "apiVersion": "v1", "data": { "lead": LEAD_SAM, "property": PROP_A, "viewsToday": 1 } },
    { "id": "evt_b4d90f2a6e17c385", "kind": "lead.search", "occurredAt": "2026-09-28T13:05:03Z", "brokerageId": "4f6c2a9e-8d31-4b57-9a0e-2c7d5e1b8f43", "apiVersion": "v1", "data": { "lead": LEAD_SAM, "search": { "type": "listings" } } }
  ]
}
```

Your endpoint answers any 2xx within 10 seconds, for example `200` with body `ok`. The body is ignored.
