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

# Events

> Webhook body fields, the 17 events, and the data format of each.

## Request format

```http theme={null}
POST /your/webhook HTTP/1.1
Content-Type: application/json
User-Agent: bottari-integration/1.0
x-bottari-signature: sha256=...
```

```json theme={null}
{
  "id": "44444444-4444-4444-8444-444444444444",
  "timestamp": "2026-01-15T02:30:12.345Z",
  "workspace_id": "exampleTeam1",
  "workspace_name": "Example team",
  "subscription_id": "exampleSub01",
  "integration_id": "exampleInt01",
  "authors": [
    {
      "id": "11111111-1111-4111-8111-111111111111",
      "type": "person"
    }
  ],
  "attempt_number": 1,
  "api_version": "2026-03-11",
  "entity": {
    "id": "exampleRow01",
    "type": "page"
  },
  "type": "page.properties_updated",
  "data": {
    "parent": {
      "id": "exampleDb001",
      "type": "database",
      "data_source_id": "exampleDb001"
    },
    "updated_properties": [
      "title",
      "exampleFld01"
    ]
  }
}
```

| Field | Meaning |
| - | - |
| `id` | Event ID. It stays the same across retries, so use it to drop duplicates. |
| `timestamp` | Time of the first change among those merged into this event. |
| `workspace_id`, `workspace_name` | Team ID and team name. |
| `subscription_id`, `integration_id` | Subscription ID and integration ID. |
| `authors` | Who made the changes. See [Authors](#authors). |
| `attempt_number` | Which attempt this is, starting at 1. |
| `api_version` | Always `2026-03-11`. |
| `entity` | What changed. `type` is `page`, `database`, `data_source`, or `comment`. |
| `type` | Event name. |
| `data` | Differs per event. |

## Event list

A subscription can choose from 17 events: 8 page, 4 database, 2 data source, and 3 comment. See [Batching](https://developers.bottari.ai/webhooks/batching.md) for timing.

### Page

Events for rows and subpages.

| Event | When | `data` | Sent |
| - | - | - | - |
| `page.created` | A row or subpage was created. | `parent` | After 60 seconds |
| `page.properties_updated` | A property value or the title changed. | `parent`, `updated_properties` | After 60 seconds |
| `page.content_updated` | The content changed. Renaming a subpage, or adding or removing an inline database, also sends this to the parent document. | `parent`, `updated_blocks` | After 60 seconds |
| `page.moved` | It moved somewhere else. | `parent` (new parent) | After 60 seconds |
| `page.deleted` | It moved to the trash. | `parent` | After 60 seconds |
| `page.undeleted` | It was restored from the trash. | `parent` | After 60 seconds |
| `page.locked` | It was locked. | `parent` | Right away |
| `page.unlocked` | It was unlocked. | `parent` | Right away |

Moving a row to another database sends `page.moved` (with the new database as `parent`) and `data_source.content_updated` for the old database.

### Database

| Event | When | `data` | Sent |
| - | - | - | - |
| `database.created` | An inline database was created in a connected document. | `parent` | After 60 seconds |
| `database.moved` | An inline database moved to another document. | `parent` (new document) | After 60 seconds |
| `database.deleted` | It moved to the trash. | `parent` | After 60 seconds |
| `database.undeleted` | It was restored from the trash. | `parent` | After 60 seconds |

Inline database events also send `page.content_updated` for the parent document. A move sends it for both documents. Deleting a document that holds an inline database sends only that document's `page.deleted`.

### Data source

| Event | When | `data` | Sent |
| - | - | - | - |
| `data_source.schema_updated` | A property was created, changed, or deleted, including its options. | `parent`, `updated_properties` | After 60 seconds |
| `data_source.content_updated` | A row left the database. | `parent`, `updated_blocks` | Right away |

There's no `data_source.created`, `moved`, `deleted`, or `undeleted`. A Bottari database has one data source, so these don't occur separately.

### Comment

These aren't sent while **Read comments** is off.

| Event | When | `data` | Sent |
| - | - | - | - |
| `comment.created` | A comment or reply was added. | `discussion_id`, `page_id`, `parent` | Right away |
| `comment.updated` | A comment was edited. Reading it back returns the new text and a new `last_edited_time`. | `discussion_id`, `page_id`, `parent` | Right away |
| `comment.deleted` | A comment was deleted. Deleting a thread with replies sends one event per deleted comment. | `discussion_id`, `page_id`, `parent` | Right away |

## Data fields

### parent

The format depends on the target. It differs from `parent` in API objects: `type` here is `database`, `page`, `space`, or `block`.

| Target | `parent` |
| - | - |
| Row | `{ "id": "<database>", "type": "database", "data_source_id": "<database>" }` |
| Subpage | `{ "id": "<parent document>", "type": "page" }` |
| Inline database | `{ "id": "<its document>", "type": "page" }` |
| Top-level database | `{ "id": "<team>", "type": "space" }` |
| Data source | `{ "id": "<database>", "type": "database" }` |
| Comment on a page | `{ "id": "<page>", "type": "page" }` |
| Comment on text or a block | `{ "id": "<block>", "type": "block" }` |
| Reply | Same as the first comment of the thread. |

For a row, `data.parent.id` and `data.parent.data_source_id` are both the database ID.

The `parent` of a comment on text follows these rules:

* It comes from the saved content. If the comment marker isn't saved yet, `parent` can be the page. Saving takes up to 10 seconds. `GET /v1/comments` reads the open content, so it answers with the block.
* If the marker disappears from the content, the old block can't be found, so `parent` is the page.
* For a comment on text in a subpage card, `parent` has the form `{document id}.{block}`. The block API returns that card as `child_page`, so reading a block with this ID returns 404. You can still use it as `block_id` in `GET /v1/comments`.

### updated\_properties

In `page.properties_updated`, this is an array of property ID strings.

* The title is `title`. Other IDs appear exactly as the API returns them.
* Formula properties don't appear.
* Changing a relation sends an event to the rows on both sides. Relations pointing at a database the integration can't read are left out.
* A value changed and restored within 60 seconds sends an empty array.

In `data_source.schema_updated`, this is an array of objects, and changes within 60 seconds merge into one event.

```json theme={null}
{
  "parent": {
    "id": "exampleDb001",
    "type": "database"
  },
  "updated_properties": [
    {
      "id": "exampleFld01",
      "name": "Owner team",
      "action": "updated"
    },
    {
      "id": "exampleFld02",
      "name": "Old notes",
      "action": "deleted"
    }
  ]
}
```

`action` is `created`, `updated`, or `deleted`. Creating two properties puts two `created` entries in one event, and renaming one property while deleting another puts `updated` and `deleted` in one event. Changing options reports that property as `updated`.

### updated\_blocks

An array of `{ id, type }` objects. `type` is `block`, `page`, or `database`; the last two are subpages and inline databases.

* It includes deleted blocks and new subpages. Reading a deleted block's ID returns 404.
* It includes blocks edited and restored within 60 seconds.
* Documents and databases the integration can't read are left out.
* Adding or removing a synced block puts it in the array, only when the integration can read it.
* Button and Bottari notes blocks are left out of content comparison. If only such a block changes outside app editing, the array can be empty.

### Comment data

```json theme={null}
{
  "discussion_id": "exampleCmt01",
  "page_id": "exampleRow01",
  "parent": { "id": "exampleRow01", "type": "page" }
}
```

`entity.id` is the comment ID, and `discussion_id` is the thread ID. They match for the first comment of a thread.

## Authors

| Who wrote | `authors` entry |
| - | - |
| A person | `{ "id": "<person>", "type": "person" }` |
| An integration | `{ "id": "<bot>", "type": "bot" }` |
| An automation, or an unknown author | `{ "id": "00000000-0000-0000-0000-000000000000", "type": "bot" }` |

When changes merge, each author appears once, in writing order. Changes written by a task agent or a session appear as the session owner, with type `person`.

> [!WARNING]
>
> Changes the integration writes itself also send events. If your server writes back after an event, skip events whose `authors` contains only the bot ID from `GET /v1/users/me`, so it doesn't loop.

Reading a row an automation wrote through the API shows `last_edited_by` as `00000000-0000-0000-0000-000000000003`, which differs from the value in webhook `authors`.

## When no event is sent

| Case | Behavior |
| - | - |
| Databases that aren't connected, rows with their own access settings | No event. |
| Changing only an icon or cover | No event. |
| Writing the same value again | No event. |
| Reordering rows, archiving, moving between spaces | No event. |
| Deleting an option, which changes row values | No row event. Only `data_source.schema_updated`. |
| Changing or deleting a property, which changes row values in bulk | No row event. |
| Resolving a comment | No event. |
| Deleting a block, which removes its comments | No comment event. |
| Deleting a whole document, which removes its comments | No comment event. |
| Editing inside a synced block | No event. |
| Relation values written by an import | No event. |
| Rows of an inline database newly created by an import | Only `database.created`, with no `page.created`. |
| Disconnecting | No event announces it. |

There are no view or file upload events.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.