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

# Entity webhooks

> Keep integrations in sync with entity changes using signed notifications.

Entity webhooks notify your integration when an entity is **created**, **updated**, or **deleted**. Each endpoint selects its actions and either all supported entity types or a list of types. Reads do not produce events.

Configure endpoints in **Workspace Settings → Entity webhooks**, or use the [webhook API](/v2-api-reference/entity-webhooks/list-entity-webhook-endpoints). Each workspace supports up to ten endpoints. Management requires an API key belonging to a workspace owner or Octave admin; changes, secret rotation, and test sends also require write API access. Read-only keys can list endpoints.

## Supported entities

Products, services, solutions, core features, segments, personas, use cases, proof points, reference customers, competitors, alternatives, buying triggers, objections, skills, brand voices, motions, and motion playbooks.

`motion` refers to the motion record. `motion_playbook` refers to a motion playbook record. Neither subscribes to all nested changes. ICPs, motion ICP cells, deprecated hypotheses, and legacy playbooks are excluded, including from **all entity types**.

Tracked collection changes notify the collection owner. Separate graph edge changes and maintenance writes that bypass normal entity persistence are not included. Soft deletion emits `entity.deleted`; restoring an entity emits `entity.updated`.

## Create an endpoint

```bash theme={null}
curl --request POST https://app.octavehq.com/api/v2/entity-webhook/create \
  --header "api_key: $OCTAVE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "name": "Entity sync",
    "url": "https://example.com/webhooks/octave",
    "enabled": true,
    "actions": ["created", "updated", "deleted"],
    "entityTypes": ["persona", "segment"]
  }'
```

Set `entityTypes` to `null` for all supported types. Empty action/type lists are rejected. The response contains `endpoint` and a one-time `secret`, plus `_metadata`. Store the secret securely. Listing or updating endpoints does not reveal it.

To update, send the complete configuration plus `oId` to `POST /api/v2/entity-webhook/update`. Set `enabled: false` to pause. Delete with `DELETE /api/v2/entity-webhook/delete?oId=whe_example`.

## Event payload

Notifications are small: fetch the entity using its type and ID for current content. A notification is not a full snapshot of the entity at that moment.

```json theme={null}
{
  "id": "evt_example",
  "object": "event",
  "apiVersion": "2026-09-29",
  "type": "entity.updated",
  "created": 1790708400,
  "workspaceOId": "wa_example",
  "data": {
    "object": { "oId": "pe_example", "entityType": "persona" },
    "changedFields": ["name", "data"],
    "revisionId": null,
    "materiality": null,
    "materialitySource": null
  }
}
```

The event types are `entity.created`, `entity.updated`, and `entity.deleted`. `changedFields` names stored top-level fields or tracked collections; it is not a nested JSON diff. Revision history is asynchronous and does not cover every change. When an exactly correlated revision is available before the first attempt, the notification includes its `revisionId`, `materiality` (`none`, `minor`, or `material`), and `materialitySource` (`deterministic` or `llm`). Otherwise these fields are `null`. Null means unavailable, not insignificant. Retries preserve the payload for that endpoint.

## Verify signatures

Every send uses HMAC-SHA256 with the endpoint secret:

| Header | Meaning |
| - | - |
| `X-Octave-Event` | Event type |
| `X-Octave-Delivery-Id` | Stable identity for this event and endpoint across retries |
| `X-Octave-Timestamp` | Signing time in Unix seconds |
| `X-Octave-Signature` | `sha256=` followed by the hex HMAC of `timestamp.rawBody` |

Verify the **raw request bytes**, before parsing JSON. Check the timestamp against a short tolerance to prevent replay, compare signatures in constant time, and deduplicate by event ID. For example, in Node.js:

```javascript theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyOctaveWebhook(rawBody, headers, secret) {
  const timestamp = headers["x-octave-timestamp"];
  const signature = headers["x-octave-signature"];
  if (typeof timestamp !== "string" || !/^\d+$/.test(timestamp)) return false;
  if (typeof signature !== "string" || !/^sha256=[a-f0-9]{64}$/.test(signature)) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = "sha256=" + createHmac("sha256", secret)
    .update(timestamp + ".")
    .update(rawBody)
    .digest("hex");
  return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
```

Rotate a secret using `POST /api/v2/entity-webhook/rotate-secret` with `{ "oId": "whe_example" }`. The new secret is shown once and applies to subsequent attempts immediately. Coordinate rotation with your receiver.

## Send a test

Use **Send test** on a saved endpoint, or:

```bash theme={null}
curl --request POST https://app.octavehq.com/api/v2/entity-webhook/test \
  --header "api_key: $OCTAVE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{ "oId": "whe_example" }'
```

The receiver gets a signed event with `test: true`, the first configured action/type (persona for all types), and `data.object.oId: "example_entity"`. Revision metadata is null. Do not try to fetch that example entity. The API responds with `status`, `responseStatus`, and `error`, plus `_metadata`; HTTP 200 alone does not mean the receiver accepted the test. Check `status: "delivered"`.

## Delivery behavior

A durable queue is written in the entity's transaction, so rolled-back writes produce no notifications. A scheduler dispatches due deliveries every minute; background consumers claim and send them. Duplicate queue messages cannot claim the same dispatch twice, and active sends are protected from concurrent retries. A crash after your receiver accepts a request can still cause another delivery, so deduplicate the stable delivery ID. Delivery retries transient failures with backoff; duplicates and out-of-order events are possible. A permanently failing endpoint can exhaust its retry budget. Return a successful response promptly and process notifications asynchronously.

Enabling an endpoint does not replay old changes. Pausing/deleting discards queued notifications when the worker reaches them. Changing filters affects future changes; queued notifications retain their original destination URL. Inspect **Workspace Settings → Notifications** for delivery receipts and failures. No ordering guarantee is made across endpoints.
