Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/trim-webhook-dead-triggers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@objectstack/spec": patch
"@objectstack/plugin-webhooks": patch
---

Trim the dead `undelete` and `api` webhook triggers (#3196). `WebhookTriggerType` declared five triggers but only three ever fired:

- `undelete` had no event source — the engine has no soft-delete/restore capability (`delete` is a hard delete; no `deleted_at` convention, no restore operation, and `data.record.undeleted` is never emitted). The `undeleted` case in the auto-enqueuer's action mapper was dead code awaiting a producer that doesn't exist.
- `api` ("manually triggered") had no fire path — the only webhook HTTP surface re-queues already-failed deliveries; nothing originates a manual fire.

Both are removed from the enum (contract-first, matching #3184/#3195): authoring a webhook on a removed trigger now fails loudly at `os validate` / registration instead of registering a webhook that silently never fires. No shipped webhook metadata used either. The auto-enqueuer now also warns when a persisted `sys_webhook` row carries a trigger it can't map to an emitted record event (a drift-guard, so a dead trigger can't silently no-op again). Reintroduce `undelete` only alongside a real restore subsystem, and `api` only alongside a real manual-fire endpoint. Updated the `sys_webhook` trigger options, field help (all locales), docs, and reference; added rejection tests.
6 changes: 3 additions & 3 deletions content/docs/automation/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ in `definition_json`, a serialised `Webhook` JSON (canonical schema:
| `id` | text | Primary key. |
| `name` | text | Unique snake_case name — referenced in logs and audit. |
| `label` | text | Optional display label. |
| `object_name` | text | Short object name whose events fire this webhook (blank = manual / API-triggered). |
| `triggers` | text | Comma-separated event list: `create,update,delete,undelete,api`. |
| `object_name` | text | Short object name whose record events fire this webhook. |
| `triggers` | text | Comma-separated event list: `create,update,delete`. |
| `url` | text | External endpoint that receives the POST. |
| `method` | text | HTTP method. Default `POST`. |
| `description` | textarea | Free-text description. |
Expand Down Expand Up @@ -321,7 +321,7 @@ Notes:

- **`object`** — short object name the event came from.
- **`recordId`** — id of the affected record.
- **`action`** — `created` / `updated` / `deleted` / `undeleted`.
- **`action`** — `created` / `updated` / `deleted`.
- **`timestamp`** — event timestamp, an ISO 8601 string (passed through
unchanged from the originating realtime event's `timestamp` field — not
an epoch-ms number).
Expand Down
36 changes: 32 additions & 4 deletions content/docs/references/automation/webhook.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,36 @@ Webhook Trigger Event

When should this webhook fire?

These mirror the record events the engine actually emits

(`data.record.created` / `updated` / `deleted`), which the webhook

auto-enqueuer maps to `create` / `update` / `delete`. Only events with a real

producer are declared here — an author can't subscribe to something that

never fires.

Deliberately NOT triggers (#3196):

- `undelete` — there is no soft-delete / restore capability in the engine

(`delete` is a hard delete; no `deleted_at` convention, no restore

operation, no `data.record.undeleted` emit), so it had no event source.

Reintroduce it only alongside a real restore subsystem that emits an

undelete event.

- `api` (manual/programmatic fire) — no manual fire path exists (the only

webhook HTTP surface re-queues already-failed deliveries). Reintroduce it

with a real "fire this webhook now" endpoint/service, not as a bare enum

value that silently never fires.

<Callout type="info">
**Source:** `packages/spec/src/automation/webhook.zod.ts`
</Callout>
Expand All @@ -33,8 +63,8 @@ const result = Webhook.parse(data);
| :--- | :--- | :--- | :--- |
| **name** | `string` | ✅ | Webhook unique name (lowercase snake_case) |
| **label** | `string` | optional | Human-readable webhook label |
| **object** | `string` | optional | Object to listen to (optional for manual webhooks) |
| **triggers** | `Enum<'create' \| 'update' \| 'delete' \| 'undelete' \| 'api'>[]` | optional | Events that trigger execution |
| **object** | `string` | optional | Object whose record events (create/update/delete) trigger this webhook |
| **triggers** | `Enum<'create' \| 'update' \| 'delete'>[]` | optional | Events that trigger execution |
| **url** | `string` | ✅ | External webhook endpoint URL |
| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'>` | ✅ | HTTP method |
| **headers** | `Record<string, string>` | optional | Custom HTTP headers |
Expand Down Expand Up @@ -75,8 +105,6 @@ const result = Webhook.parse(data);
* `create`
* `update`
* `delete`
* `undelete`
* `api`


---
Expand Down
8 changes: 4 additions & 4 deletions content/docs/references/integration/connector.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ Circuit breaker configuration
| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | |
| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration |
| **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record<string, any> }; defaultValue?: any; … }[]` | optional | Field mapping rules |
| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete' \| 'undelete' \| 'api'>[]; … }[]` | optional | Webhook configurations |
| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations |
| **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration |
| **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration |
| **connectionTimeoutMs** | `number` | optional | Connection timeout in ms |
Expand Down Expand Up @@ -310,7 +310,7 @@ Connector type
| **triggers** | `{ key: string; label: string; description?: string; type: Enum<'polling' \| 'webhook'>; … }[]` | optional | |
| **syncConfig** | `{ strategy?: Enum<'full' \| 'incremental' \| 'upsert' \| 'append_only'>; direction?: Enum<'import' \| 'export' \| 'bidirectional'>; schedule?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; realtimeSync?: boolean; … }` | optional | Data sync configuration |
| **fieldMappings** | `{ source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record<string, any> }; defaultValue?: any; … }[]` | optional | Field mapping rules |
| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete' \| 'undelete' \| 'api'>[]; … }[]` | optional | Webhook configurations |
| **webhooks** | `{ name: string; label?: string; object?: string; triggers?: Enum<'create' \| 'update' \| 'delete'>[]; … }[]` | optional | Webhook configurations |
| **rateLimitConfig** | `{ strategy?: Enum<'fixed_window' \| 'sliding_window' \| 'token_bucket' \| 'leaky_bucket'>; maxRequests: number; windowSeconds: number; burstCapacity?: number; … }` | optional | Rate limiting configuration |
| **retryConfig** | `{ strategy?: Enum<'exponential_backoff' \| 'linear_backoff' \| 'fixed_delay' \| 'no_retry'>; maxAttempts?: number; initialDelayMs?: number; maxDelayMs?: number; … }` | optional | Retry configuration |
| **connectionTimeoutMs** | `number` | optional | Connection timeout in ms |
Expand Down Expand Up @@ -465,8 +465,8 @@ Synchronization strategy
| :--- | :--- | :--- | :--- |
| **name** | `string` | ✅ | Webhook unique name (lowercase snake_case) |
| **label** | `string` | optional | Human-readable webhook label |
| **object** | `string` | optional | Object to listen to (optional for manual webhooks) |
| **triggers** | `Enum<'create' \| 'update' \| 'delete' \| 'undelete' \| 'api'>[]` | optional | Events that trigger execution |
| **object** | `string` | optional | Object whose record events (create/update/delete) trigger this webhook |
| **triggers** | `Enum<'create' \| 'update' \| 'delete'>[]` | optional | Events that trigger execution |
| **url** | `string` | ✅ | External webhook endpoint URL |
| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'>` | ✅ | HTTP method |
| **headers** | `Record<string, string>` | optional | Custom HTTP headers |
Expand Down
Loading