# Webhook endpoints

6 endpoints.

## List a workspace's webhook endpoints

```http
GET https://orbitwrite.com/api/v1/webhook-endpoints
```

Returns endpoints (never the signing secret — only its masked hint) plus the workspace's tags for the optional tag filter. Requires `webhook:read`, which every role holds.

**Authentication:** bearer token or session cookie.

### Query parameters

- `organizationId` (string, optional) — Session callers only — an API key already determines the workspace.

### Responses

#### 200

The workspace's webhook endpoints

- `endpoints` (WebhookEndpoint[], required)
  - `id` (string, required)
  - `label` (string | null, required)
  - `url` (string, required)
  - `secretHint` (string | null, required) — Masked secret preview — the full value is never returned
  - `events` (string[], required)
  - `tagId` (string | null, required)
  - `enabled` (boolean, required)
- `tags` (WebhookTag[], required) — The workspace's tags, for the optional per-endpoint tag filter. Empty for a caller without `tag:read` — the endpoints are still returned.
  - `id` (string, required)
  - `name` (string, required)
  - `color` (string, required)

#### 400

Invalid request

- `error` (string, required)
- `issues` (any[], optional)

#### 401

Not signed in

- `error` (string, required)
- `issues` (any[], optional)

#### 403

Not a member of the organization

- `error` (string, required)
- `issues` (any[], optional)

## Add a webhook endpoint

```http
POST https://orbitwrite.com/api/v1/webhook-endpoints
```

Registers a receiver and returns its signing secret **exactly once** — there is no route that returns it again, so store it now. Requires `webhook:create` (owner/admin, or a key scoped to it).

**Authentication:** bearer token or session cookie.

### Request body (`application/json`)

- `url` (string, required)
- `events` (string[], required)
- `label` (string | null, optional)
- `tagId` (string | null, optional) — Narrow the rule to posts carrying this tag. Null (or omitted) means all posts; a tag from another workspace is rejected.
- `organizationId` (string, optional) — Required for session callers with no active organization. Ignored for API keys, which are already scoped to one workspace.

### Responses

#### 200

The endpoint was added; `secret` is shown once

- `ok` (boolean, required, one of `true`)
- `secret` (string, required) — The signing secret — surfaced exactly once, never retrievable again
- `endpoint` (WebhookEndpoint, required)
  - `id` (string, required)
  - `label` (string | null, required)
  - `url` (string, required)
  - `secretHint` (string | null, required) — Masked secret preview — the full value is never returned
  - `events` (string[], required)
  - `tagId` (string | null, required)
  - `enabled` (boolean, required)

#### 400

Invalid request

- `error` (string, required)
- `issues` (any[], optional)

#### 401

Not signed in

- `error` (string, required)
- `issues` (any[], optional)

#### 403

Not a member of the organization

- `error` (string, required)
- `issues` (any[], optional)

## Enable or pause a webhook endpoint

```http
POST https://orbitwrite.com/api/v1/webhook-endpoints/{endpointId}/toggle
```

Requires `webhook:update`. A paused endpoint keeps its secret and routing but receives nothing.

**Authentication:** bearer token or session cookie.

### Path parameters

- `endpointId` (string, required)

### Request body (`application/json`)

- `organizationId` (string, optional) — Required for session callers with no active organization. Ignored for API keys, which are already scoped to one workspace.
- `enabled` (boolean, required)

### Responses

#### 200

The mutation was applied

- `ok` (boolean, required, one of `true`)
- `endpoint` (WebhookEndpoint, required)
  - `id` (string, required)
  - `label` (string | null, required)
  - `url` (string, required)
  - `secretHint` (string | null, required) — Masked secret preview — the full value is never returned
  - `events` (string[], required)
  - `tagId` (string | null, required)
  - `enabled` (boolean, required)

#### 400

Invalid request

- `error` (string, required)
- `issues` (any[], optional)

#### 401

Not signed in

- `error` (string, required)
- `issues` (any[], optional)

#### 403

Not a member of the organization

- `error` (string, required)
- `issues` (any[], optional)

#### 404

The resource does not exist

- `error` (string, required)
- `issues` (any[], optional)

## Rotate an endpoint's signing secret

```http
POST https://orbitwrite.com/api/v1/webhook-endpoints/{endpointId}/regenerate-secret
```

The old secret stops verifying immediately (its ciphertext is overwritten) and the new one is surfaced once here and never again, so update the receiver in the same breath. Requires `webhook:update` — the same authority that can already re-point the endpoint.

**Authentication:** bearer token or session cookie.

### Path parameters

- `endpointId` (string, required)

### Request body (`application/json`)

- `organizationId` (string, optional) — Required for session callers with no active organization. Ignored for API keys, which are already scoped to one workspace.

### Responses

#### 200

The secret was rotated; the new value is shown once

- `ok` (boolean, required, one of `true`)
- `secret` (string, required) — The signing secret — surfaced exactly once, never retrievable again
- `endpoint` (WebhookEndpoint, required)
  - `id` (string, required)
  - `label` (string | null, required)
  - `url` (string, required)
  - `secretHint` (string | null, required) — Masked secret preview — the full value is never returned
  - `events` (string[], required)
  - `tagId` (string | null, required)
  - `enabled` (boolean, required)

#### 400

Invalid request

- `error` (string, required)
- `issues` (any[], optional)

#### 401

Not signed in

- `error` (string, required)
- `issues` (any[], optional)

#### 403

Not a member of the organization

- `error` (string, required)
- `issues` (any[], optional)

#### 404

The resource does not exist

- `error` (string, required)
- `issues` (any[], optional)

## Update a webhook endpoint

```http
PATCH https://orbitwrite.com/api/v1/webhook-endpoints/{endpointId}
```

Edits everything except the secret, which rotates through its own route. Requires `webhook:update`.

Despite the verb this **replaces** the editable fields rather than merging them: `url` and `events` are required, and `label` and `tagId` are optional only in the sense that omitting one (or sending `null`) *clears* it — it does not leave the stored value alone. Send the endpoint's full desired state.

**Authentication:** bearer token or session cookie.

### Path parameters

- `endpointId` (string, required)

### Request body (`application/json`)

- `url` (string, required)
- `events` (string[], required)
- `label` (string | null, optional)
- `tagId` (string | null, optional) — Narrow the rule to posts carrying this tag. Null (or omitted) means all posts; a tag from another workspace is rejected.
- `organizationId` (string, optional) — Required for session callers with no active organization. Ignored for API keys, which are already scoped to one workspace.

### Responses

#### 200

The mutation was applied

- `ok` (boolean, required, one of `true`)
- `endpoint` (WebhookEndpoint, required)
  - `id` (string, required)
  - `label` (string | null, required)
  - `url` (string, required)
  - `secretHint` (string | null, required) — Masked secret preview — the full value is never returned
  - `events` (string[], required)
  - `tagId` (string | null, required)
  - `enabled` (boolean, required)

#### 400

Invalid request

- `error` (string, required)
- `issues` (any[], optional)

#### 401

Not signed in

- `error` (string, required)
- `issues` (any[], optional)

#### 403

Not a member of the organization

- `error` (string, required)
- `issues` (any[], optional)

#### 404

The resource does not exist

- `error` (string, required)
- `issues` (any[], optional)

## Remove a webhook endpoint

```http
DELETE https://orbitwrite.com/api/v1/webhook-endpoints/{endpointId}
```

Requires `webhook:delete`. Deliveries stop immediately and the signing secret goes with the row.

**Authentication:** bearer token or session cookie.

### Path parameters

- `endpointId` (string, required)

### Query parameters

- `organizationId` (string, optional) — Session callers only — an API key already determines the workspace.

### Responses

#### 200

The endpoint was removed

- `ok` (boolean, required, one of `true`)

#### 400

Invalid request

- `error` (string, required)
- `issues` (any[], optional)

#### 401

Not signed in

- `error` (string, required)
- `issues` (any[], optional)

#### 403

Not a member of the organization

- `error` (string, required)
- `issues` (any[], optional)

#### 404

The resource does not exist

- `error` (string, required)
- `issues` (any[], optional)
