---
title: "Events"
description: "Every webhook event OnePageCRM sends — the entity × reason matrix, dynamic deal status events, triggers, and privacy exclusions."
canonical_url: https://developer.onepagecrm.com/webhooks/events/
source: Markdown mirror of https://developer.onepagecrm.com/webhooks/events/
---

Every webhook carries a `type` (the entity that changed) and a
`reason` (what happened to it). This page lists every combination.

## The event matrix

| `reason` | contact | company | deal | action | note | call | meeting |
| --- | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
| `created` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `updated` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `deleted` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `undeleted` | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ |
| `completed` | — | — | — | ✓ | — | — | — |
| `uncompleted` | — | — | — | ✓ | — | — | — |
| `expected_close_date_updated` | — | — | ✓ | — | — | — | — |
| `changed_to_<status>` | — | — | ✓ | — | — | — | — |

Reasons are lowercase plain strings, exactly as shown.

Note the one asymmetry: **companies have no `undeleted` event**.
Every other entity sends one when a deleted record is restored.

## Reason reference

| `reason` | Fires when |
| --- | --- |
| `created` | The record is created. |
| `updated` | The record is edited. |
| `deleted` | The record is deleted. `data` contains only the `id` — see [Payloads](/webhooks/payloads/#deleted-events). |
| `undeleted` | A deleted record is restored. |
| `completed` | An action is marked done. |
| `uncompleted` | A completed action is reopened. |
| `expected_close_date_updated` | A pending deal's expected close date changes. |
| `changed_to_<status>` | A deal's status changes — see below. |

## Dynamic deal status events

`changed_to_<status>` fires when a deal's status changes, with the
new status substituted in. Deal status is constrained to exactly
three values — `pending`, `won`, `lost` — so the possible reasons
are:

- `changed_to_won` — deal marked won
- `changed_to_lost` — deal marked lost
- `changed_to_pending` — deal moved back to pending

Pipeline stages are a separate concept and do not produce
`changed_to_` events. Matching on the `changed_to_` prefix and
treating the suffix as data is still a reasonable way to
future-proof your receiver — but today the suffix is always one of
those three.

## What triggers webhooks

Changes fire the same events no matter where they come from:

| Source | Fires webhooks? |
| --- | --- |
| The OnePageCRM web app | Yes |
| The [REST API v3](/api/reference/) | Yes |
| The [MCP server](/mcp/overview/) | Yes |
| Bulk operations (bulk status, tag, or owner updates) | Yes — **one event per affected record** |
| CSV, Google Contacts, or Highrise imports | **No** |

Two things to plan for:

- A bulk update of 500 contacts means 500 `contact` / `updated`
  events arriving at your endpoint.
- Imports are silent. If your integration needs imported records,
  reconcile with a periodic API poll — see
  [Delivery](/webhooks/delivery/).

## No deduplication

There is no dedupe window. Three rapid edits to the same contact
produce three `updated` events. Build your receiver to be
idempotent — `type` + the entity id + `timestamp` makes a good
deduplication key. The entity id lives at `data.<type>.id`
(`data.contact.id`, `data.deal.id`, ...), or `data.id` for
`deleted` events. See [Delivery](/webhooks/delivery/) for the
pattern.

## Privacy exclusions

Webhooks respect contact privacy:

- No `created`, `updated`, or other full-payload webhook is sent
  for a **private contact**, or for any record (deal, note, action,
  call, meeting) belonging to a private contact.
- **Company** events are skipped if the company has no contacts, or
  if any of its contacts is private.
- **`deleted` events are the exception** — they are still sent for
  private contacts. The privacy check never runs for deletes because
  `data` carries only the `id`, nothing private.

If you're testing and an event never arrives, check whether the
record's contact is private before debugging your endpoint.
