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

# Grow CRM Webhook Event Reference: All 15 Resources

> Complete Grow CRM webhook event reference: the five-key model, discriminator fields, all 15 resources, date formats, and import batches.

Every Grow CRM webhook event shares the same envelope — `{ event, id, created, data }` — described in [Introduction](/introduction). What changes between events is the value of `event` and the shape of `data`. Rather than exposing a long flat list of narrow event keys, Grow CRM groups related changes under five keys per resource and uses a **discriminator field** inside `data` to tell you exactly what happened. This page explains the model in full; for a specific resource's exact payload fields and discriminator values, follow the link in the Resources table below.

## The five-key model

Every resource exposes up to five subscribable event keys. Subscribe to a key once and branch on the discriminator — you do not need a separate subscription per change type.

| Key                   | Fires when                                                                                                 | Discriminator               | `data` shape                                                         |
| --------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------- | -------------------------------------------------------------------- |
| `<resource>.created`  | A record is created, including via clone or duplicate                                                      | —                           | The full resource object                                             |
| `<resource>.updated`  | A **significant** change is made to an existing record                                                     | `data.change`               | `change` + the full resource object nested under the resource's name |
| `<resource>.deleted`  | A record is deleted                                                                                        | —                           | `{ "id": <deleted id> }`                                             |
| `<resource>.sundry`   | A **minor** field edit (cosmetic or descriptive)                                                           | `data.field`                | `field` + the full resource object nested under the resource's name  |
| `<resource>.activity` | A child record is added, updated, or deleted (comment, attachment, note, checklist, milestone, log, reply) | `data.type` + `data.action` | `type`, `action`, `item`                                             |
| `<resource>.imported` | A bulk import finishes — **only on the four importable resources**                                         | —                           | The imported batch (see [Imports](#imports))                         |

## Discriminator fields

Use the discriminator values to branch your handler logic without subscribing to multiple keys:

* **`.updated`** — branch on `data.change`. Every `.updated` delivery carries the complete, current resource object in `data.<resource>` — not a diff. Whatever single thing changed, the object reflects the record's full state after the change.
* **`.sundry`** — branch on `data.field`. As with `.updated`, `data.<resource>` always contains the full current object.
* **`.activity`** — branch on both `data.type` and `data.action`. The value of `data.type` determines the shape of `data.item`. Supported types include `comment`, `attachment`, `note`, `checklist`, `checklist_comment`, `milestone`, `task`, `log`, and `reply` — see each resource's own page for which types it produces. `data.action` is normally `added`, `updated`, or `deleted`; checklists additionally use `status_changed` for ticking an item off.

<Note>
  Deletion events (`*.deleted`) fire **after** the record has already been removed. Because the record no longer exists to describe, `data` contains only `{ "id": <deleted id> }`. The same applies to `.activity` delete actions for some child record types — see each resource's page for which delete actions carry a full item versus just `{ "id": ... }`.
</Note>

## Date formatting

Date and time values inside `data` follow two different formats depending on the field type:

* **ISO 8601 with microseconds** (`2026-07-13T10:15:00.000000Z`) — used for any `created` or `updated` timestamp that represents a record's own creation or update time. This includes `dates.created` and `dates.updated` on resource objects, and the `created` field on `.activity` items such as comments, attachments, logs, and replies. These fields are always serialised as microsecond-precision UTC strings.
* **Raw database format** — used for every other date or time field. Timestamps appear as `Y-m-d H:i:s`; date-only fields appear as `Y-m-d`. Examples include due dates, sent dates, expiry dates, and a ticket's `last_updated` field.

<Warning>
  Do not infer the format from the field name alone. Check each resource's own reference page to confirm whether a specific field uses ISO 8601 or the raw database format.
</Warning>

## Resources

The table below lists all 15 resources and their available event keys. Follow each link for full payload shapes, discriminator values, and example JSON.

| Resource   | Available keys                                        | Details                              |
| ---------- | ----------------------------------------------------- | ------------------------------------ |
| Clients    | created, updated, deleted, sundry, activity, imported | [Clients →](/webhooks/clients)       |
| Leads      | created, updated, deleted, sundry, activity, imported | [Leads →](/webhooks/leads)           |
| Projects   | created, updated, deleted, sundry, activity, imported | [Projects →](/webhooks/projects)     |
| Tasks      | created, updated, deleted, sundry, activity           | [Tasks →](/webhooks/tasks)           |
| Estimates  | created, updated, deleted, sundry, activity           | [Estimates →](/webhooks/estimates)   |
| Proposals  | created, updated, deleted, sundry                     | [Proposals →](/webhooks/proposals)   |
| Contracts  | created, updated, deleted, sundry                     | [Contracts →](/webhooks/contracts)   |
| Invoices   | created, updated, deleted, sundry, activity           | [Invoices →](/webhooks/invoices)     |
| Payments   | created, updated, deleted, sundry                     | [Payments →](/webhooks/payments)     |
| Refunds    | created, updated, deleted, sundry                     | [Refunds →](/webhooks/refunds)       |
| Products   | created, updated, deleted, sundry, activity, imported | [Products →](/webhooks/products)     |
| Tickets    | created, updated, deleted, sundry, activity           | [Tickets →](/webhooks/tickets)       |
| Expenses   | created, updated, deleted, sundry, activity           | [Expenses →](/webhooks/expenses)     |
| Timesheets | created, updated, deleted                             | [Timesheets →](/webhooks/timesheets) |
| Team       | created, updated, deleted                             | [Team →](/webhooks/team)             |

<Note>
  Proposals, Contracts, Payments, and Refunds have no `.activity` key — none of those resources has comments, attachments, or logs. Timesheets and Team are lifecycle-only: time entries have no minor fields and no child records, and all team member changes arrive as `team.updated`.
</Note>

## Imports

Records created by a **bulk import** do not fire `<resource>.created`. Importers write rows directly to the database rather than going through the normal create path — a 5,000-row import would otherwise produce 5,000 separate deliveries. Instead, the four importable resources (Clients, Leads, Projects, and Products) fire a single `<resource>.imported` event that carries the entire batch.

The envelope `id` for an import event is the import's **reference string**, not a numeric record id. This is the only place in the webhook system where `id` is not an integer.

```json title="Import payload example" theme={null}
{
  "event": "project.imported",
  "id": "8f3ka92m",
  "created": "2026-07-31T10:00:00+00:00",
  "data": {
    "import_ref": "8f3ka92m",
    "imported_by": 3,
    "batch": 1,
    "batch_count": 3,
    "totals": { "imported": 2400, "skipped": 12, "errors": 3 },
    "records": [
      {
        "id": 1234,
        "title": "Acme website rebuild",
        "client_id": 12,
        "created": "2026-07-31T10:00:00.000000Z"
      }
    ]
  }
}
```

### Import payload fields

| Field           | Type            | Notes                                                                                                          |
| --------------- | --------------- | -------------------------------------------------------------------------------------------------------------- |
| `id` (envelope) | string          | The import reference — **not** a numeric record id. This is the one place the envelope `id` is not an integer. |
| `import_ref`    | string          | Same value as the envelope `id`; identifies this specific import run across all its batches.                   |
| `imported_by`   | integer \| null | Id of the user who ran the import.                                                                             |
| `batch`         | integer         | 1-based index of this delivery within the import run.                                                          |
| `batch_count`   | integer         | Total number of deliveries this import will produce.                                                           |
| `totals`        | object          | `imported`, `skipped`, and `errors` counts for the **whole** import — repeated on every batch delivery.        |
| `records`       | array           | Compact records for this batch. See each resource's page for the exact fields included.                        |

### Batching rules

Large imports are split across multiple deliveries of up to 1,000 records each, so no single request body grows large enough to be rejected by a receiver with a strict maximum body-size limit. Every batch belonging to the same import shares the same `import_ref`, and the `batch` / `batch_count` fields tell you how to reassemble them.

<Tip>
  Batches are queued in id order and delivered independently. Treat the import as complete only once you have received `batch_count` deliveries sharing the same `import_ref`. Records in each batch are deliberately compact — enough to identify each row and match it to your own data. If you need the full record, fetch it from the API using its `id`.
</Tip>

<Note>
  Checklist imports do not fire an import event. Checklists create child records rather than top-level resources, so individual checklist rows are associated with their parent records without triggering a batch delivery.
</Note>
