Webhook
Webhook protocol schemas
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.
Bulk triggers (#4639). bulk_update / bulk_delete map to the engine's
aggregate data.records.updated / data.records.deleted, emitted when a
predicate write (multi: true → IDataDriver.updateMany/deleteMany)
affects a set of rows the driver reports only as a count. They are separate
trigger values, not extra sources for update / delete, because their
delivery has a different SHAPE: no recordId, no record body, just
object + matched. Folding them into the per-record triggers would send
every existing subscriber a body missing the fields it reads — the same
class of breakage as the pre-#4626 recordId: '' fabrication, arriving from
the other direction. A webhook that wants both subscribes to both.
Deliberately NOT triggers (#3196):
undelete— there is no soft-delete / restore capability in the engine (deleteis a hard delete; nodeleted_atconvention, no restore operation, nodata.record.undeletedemit), 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.
Source: packages/spec/src/automation/webhook.zod.ts
TypeScript Usage
import { WebhookSchema, WebhookTriggerType } from '@objectstack/spec/automation';
import type { Webhook, WebhookTriggerType } from '@objectstack/spec/automation';
// Validate data
const result = WebhookSchema.parse(data);Webhook
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Webhook unique name (lowercase snake_case) |
| label | string | optional | Human-readable webhook label |
| object | string | optional | Object whose record events (create/update/delete, bulk_update/bulk_delete) trigger this webhook |
| triggers | Enum<'create' | 'update' | 'delete' | 'bulk_update' | 'bulk_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 |
| timeoutMs | integer | ✅ | Request timeout in milliseconds |
| secret | string | optional | Signing secret for HMAC signature verification |
| isActive | boolean | ✅ | Whether webhook is active |
| description | string | optional | Webhook description |
| protection | { lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string } | optional | Package author protection block — lock policy for this webhook. |
| _lock | Enum<'none' | 'no-overlay' | 'no-delete' | 'full'> | optional | Item-level lock — controls overlay & delete (ADR-0010). |
| _lockReason | string | optional | Human-readable reason shown when a write is refused by _lock. |
| _lockSource | Enum<'artifact' | 'package' | 'env-forced'> | optional | Layer that set _lock (artifact | package | env-forced). |
| _provenance | Enum<'package' | 'org' | 'env-forced'> | optional | Origin of the item (package | org | env-forced). |
| _packageId | string | optional | Owning package machine id. |
| _packageVersion | string | optional | Owning package version. |
| _lockDocsUrl | string | optional | Optional documentation link surfaced next to _lockReason. |
WebhookTriggerType
Allowed Values
createupdatedeletebulk_updatebulk_delete