> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.livingbrain.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.livingbrain.com/_mcp/server.

# Change feed and webhooks

Two ways to find out what changed: poll the change feed, or subscribe to webhooks. Most
integrations use webhooks for latency and the feed for reconciliation.

## The change feed

Every meaningful mutation emits an event with an `action`:

`created` · `updated` · `renamed` · `merged` · `split` · `archived` · `restored` · `recovered` · `routed`

**`TypeScript`**

```typescript TypeScript
for await (const ev of await client.workspace.changes({ brainId, since: lastSyncedAt })) {
  console.log(ev.action, ev.pageId, ev.fromVersion, "→", ev.toVersion);
}
```

**`Python`**

```python Python
for ev in client.workspace.changes(brain_id=brain_id, since=last_synced_at):
    print(ev.action, ev.page_id, ev.from_version, "→", ev.to_version)
```

Filter by `pageId` to watch one page, or `unackedOnly` to build an inbox.

### Acknowledging

**`TypeScript`**

```typescript TypeScript
await client.workspace.ackChange({ brainId, changeId });
const { acked } = await client.workspace.ackAll({ brainId });
```

**`Python`**

```python Python
client.workspace.ack_change(brain_id=brain_id, change_id=change_id)
result = client.workspace.ack_all(brain_id=brain_id)
```

> **Info**
>
> Acknowledgement is a **user-facing** read marker, not a delivery receipt. For machine sync,
> track `since` yourself — acking is for "the human has seen this."

## Webhooks

Register an endpoint and receive events as they happen.

**`TypeScript`**

```typescript TypeScript
const { secret, ...sub } = await client.webhooks.create({
  url: "https://api.example.com/hooks/livingbrain",
  events: ["brain.page.updated", "brain.change.new", "brain.connection.new"],
});
// Store `secret` now — it is never returned again.
```

**`Python`**

```python Python
sub = client.webhooks.create(
    url="https://api.example.com/hooks/livingbrain",
    events=["brain.page.updated", "brain.change.new", "brain.connection.new"],
)
# Store sub.secret now — it is never returned again.
```

> **Warn**
>
> The signing secret is returned **only on creation**. If you lose it, delete the subscription
> and register a new one.

### Events

| Event                   | Fires when                           |
| ----------------------- | ------------------------------------ |
| `brain.page.updated`    | A page's content or metadata changed |
| `brain.change.new`      | A new change event was recorded      |
| `brain.connection.new`  | A new connection was discovered      |
| `brain.brief.ready`     | A daily brief finished generating    |
| `brain.import.progress` | A long-running import advanced       |

### Managing subscriptions

**`TypeScript`**

```typescript TypeScript
const subs = await client.webhooks.list();
await client.webhooks.update({ id, events: ["brain.brief.ready"] });
await client.webhooks.remove({ id });
```

**`Python`**

```python Python
subs = client.webhooks.list()
client.webhooks.update(id=id, events=["brain.brief.ready"])
client.webhooks.remove(id=id)
```

Listing never includes secrets. Updating a subscription leaves its secret unchanged.

## Daily briefs

A brain can generate a scheduled digest. Configure the schedule, or trigger one on demand:

**`TypeScript`**

```typescript TypeScript
await client.brains.updateBriefSettings({
  brainId,
  briefSettings: { enabled: true, hourLocal: 8, timezone: "America/New_York" },
});
const brief = await client.brains.runBrief({ brainId });
```

**`Python`**

```python Python
client.brains.update_brief_settings(
    brain_id=brain_id,
    brief_settings={"enabled": True, "hour_local": 8, "timezone": "America/New_York"},
)
brief = client.brains.run_brief(brain_id=brain_id)
```

Subscribe to `brain.brief.ready` rather than polling — generation is not instant.