API · Endpoints

Webhooks

Subscribe to events at the org level. Each delivery is signed with the webhook's secret using HMAC-SHA256; verify the signature before trusting the payload.

Generated from src/lib/api-paths/webhooks.ts. The events list comes from CreateWebhookSchema in the runtime, what's documented here is what the API accepts.

get/api/orgs/{slug}/webhooks

List org webhooks

Every webhook configured for the org. Secrets are returned as a preview only.

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes

Responses

StatusBodyDescription
200objectWebhook list.

post/api/orgs/{slug}/webhooks

Create a webhook

Register a webhook subscription. The full signing secret is returned exactly once in the response — store it before the response is closed.

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes

Request body

FieldTypeRequiredDescription
urlstring (uri)yes
events"row.created" | "row.updated" | "row.deleted" | "row.sealed" | "comment.added" | "comment.deleted" | "member.invited" | "member.joined" | "member.removed" | "member.role_changed" | "workspace.created" | "workspace.renamed" | "workspace.columns_updated" | "workspace.visibility_changed" | "workspace.archived" | "doc.created" | "doc.updated" | "doc.heading_added" | "doc.mention_added" | "message.sent" | "file.uploaded" | "file.renamed" | "file.moved" | "file.archived" | "file.restored" | "file.version_replaced" | "file.starred" | "file.tagged" | "file.described" | "file.shared" | "file.share_revoked" | "file.commented" | "folder.created" | "folder.archived"[]no

Responses

StatusBodyDescription
200objectCreated.
400ErrorURL failed SSRF guard (loopback, private range, cloud metadata).

patch/api/orgs/{slug}/webhooks/{id}

Toggle webhook active state

Enable or disable a webhook without deleting it. Body: `{ active: boolean }`.

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes
idstringyes

Request body

FieldTypeRequiredDescription
activebooleanyes

Responses

StatusBodyDescription
200objectUpdated.
404ErrorNot found.

delete/api/orgs/{slug}/webhooks/{id}

Delete a webhook

Removes the subscription. In-flight deliveries are cancelled.

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes
idstringyes

Responses

StatusBodyDescription
200objectDeleted.
404ErrorNot found.

get/api/orgs/{slug}/webhooks/{id}/deliveries

List recent webhook deliveries

Up to 50 most recent delivery attempts for one webhook, newest first. Use to debug failed integrations or watch retry status.

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes
idstringyes

Responses

StatusBodyDescription
200objectDelivery list.
404ErrorWebhook not found.

post/api/orgs/{slug}/webhooks/{id}/rotate-secret

Rotate webhook signing secret

Generate a fresh signing secret. Returned exactly once. Subsequent deliveries are signed with the new secret only — update your receiver before the next event lands.

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes
idstringyes

Responses

StatusBodyDescription
200objectRotated.
404ErrorNot found.

Events

Pass any subset on events at create time. Adding new event names later requires a schema update in src/lib/schemas.ts + Settings UI; the runtime rejects unknown names.

EventWhen it fires
row.createdA new row is appended.
row.updatedAny cell on a row is changed. Includes the diff.
row.deletedA row is deleted.
row.sealedA row is sealed (writes locked).
comment.addedA comment is created on any anchor type.
comment.deletedA comment is deleted.
member.invitedAn invite is created (workspace or org).
member.joinedAn invitee accepts and a membership row is created.
member.removedA member is removed from a workspace, including the cascade from an org-removal.
member.role_changedA member's role changes (workspace or org).
workspace.createdA workspace is created.
workspace.renamedA workspace's name (or slug) changes.
workspace.columns_updatedColumns are added, removed, reordered, or retyped on a table surface.
workspace.visibility_changedWorkspace visibility flips between private / org / unlisted / public.
workspace.archivedA workspace is archived (soft-delete).
doc.createdA workspace is created in doc mode.
doc.updatedA doc body is replaced or appended-to.
doc.heading_addedA new heading is written in a doc body. Useful for outline-aware downstream pipelines.
doc.mention_addedA user or agent is @-mentioned in a doc body. Payload carries `mentions: [{ kind: "user" | "agent", id, label }]` for newly-added mentions only, re-saving a doc that already mentions someone does not re-fire. Mentioned humans also get an inbox row + email; mentioned agents get this webhook.
file.uploadedA new file row is committed to a Files surface (via direct or proxy upload). Payload: `{ fileId, name, mimeType, size, surfaceId, parentFolderId }`.
file.renamedPATCH /files/{id} { name } changed the display name. blobKey stays stable. Payload: `{ fileId, name, previousName, surfaceId }`.
file.movedPATCH /files/{id} { parentFolderId } moved a file within its surface. Cross-surface moves reject. Payload: `{ fileId, parentFolderId, previousParentFolderId, surfaceId }`.
file.archivedDELETE /files/{id} soft-deleted a file (30-day trash window). Payload: `{ fileId, name, surfaceId }`.
file.restoredPATCH /files/{id} { restore: true } un-archived a soft-deleted file. Payload: `{ fileId, name, surfaceId }`.
file.version_replacedPOST /files/{id}/versions replaced bytes (same id, new sha256). Payload: `{ fileId, name, previousSha256, sha256, size, surfaceId }`.
file.starredPATCH /files/{id}/metadata { starred } flipped the workspace-scoped star state. Payload: `{ fileId, name, starred, surfaceId }`.
file.taggedPATCH /files/{id}/metadata { tags } updated the tag set. Payload: `{ fileId, name, tags, surfaceId }`.
file.describedPATCH /files/{id}/metadata { description } updated the free-text caption. Payload: `{ fileId, name, description, surfaceId }`.
file.sharedPOST /files/{id}/share minted a public share token. Payload: `{ fileId, name, shareTokenId, url, surfaceId }`.
file.share_revokedDELETE /files/{id}/share revoked a previously-minted token. Payload: `{ fileId, shareTokenId, surfaceId }`.
file.commentedA comment was added with `target.type="file"`. Fires alongside the generic `comment.added` so file-focused subscribers don't have to filter the firehose. Payload: `{ fileId, name, surfaceId, commentId, parentId, body, mentions }`.
folder.createdPOST /folders created a folder. Payload: `{ folderId, name, parentFolderId, surfaceId }`.
folder.archivedDELETE /folders/{id} cascade-archived a folder and its descendants. Payload: `{ folderId, name, surfaceId, cascaded }` (cascaded = count of files moved to trash).

Signing

Every delivery includes a Dock-Signature header of the form t=<ts>,v1=<hex>. Compute HMAC_SHA256(secret, ts + "." + body); constant-time-compare against v1; reject if ts is more than 5 minutes off your clock.

For 24h after a secret rotation, deliveries carry a second signature v2=<hex> signed with the previous secret. Verify against v1 first; if it fails, fall back to v2. After 24h, only v1 is sent. This gives you a safe window to swap your stored secret without dropping events in flight.

Frequently asked questions

How do I register a Dock webhook to react to row changes?
POST `/api/webhooks` with `{ url, events: ['row.created', 'row.updated'] }`. URL must be public HTTPS (loopback, RFC1918 private ranges, cloud metadata addresses are blocked). Returns the signing secret exactly once.
What events does Dock emit via webhooks?
row.* (created/updated/deleted/sealed/moved_surface), doc.* (created/updated/heading_added/mention_added), comment.* (added/deleted), file.* (uploaded/renamed/moved/archived/restored/version_replaced/starred/tagged/described/shared/share_revoked/commented), folder.* (created/archived), member.* (invited/joined/removed/role_changed), workspace.* (created/renamed/columns_updated/visibility_changed/archived/unarchived), surface.* (created/updated/archived). For agent-routing on @-mentions: subscribe to `doc.mention_added` for doc-body pings and read the `mentions` array on `comment.added` for comment pings.
How do I verify a Dock webhook signature?
Read the `Dock-Signature` header (format `t=<unix-ts>,v1=<hex>`). HMAC-SHA256 the body with your signing secret prefixed by `<ts>.`; constant-time-compare against `v1`. Reject if `t` is more than 5 minutes off your clock (replay protection).
How do I rotate a Dock webhook signing secret?
POST `/api/webhooks/:id/rotate-secret`. New secret returned exactly once. For 24h after rotation, every delivery carries BOTH the new signature (`v1=…`) and the previous one (`v2=…`) so receivers have a safe window to swap their stored secret without dropping in-flight events. After 24h, only `v1` is sent.
How do I temporarily pause a Dock webhook?
PATCH `/api/webhooks/:id` with `{ active: false }`. Inactive webhooks skip delivery (no retry queue, no log row), but the URL + secret + event subscription are preserved. Flip back with `{ active: true }`.
How do I delete a Dock webhook permanently?
DELETE `/api/webhooks/:id`. URL stops receiving events immediately and the secret is destroyed; recreate from scratch if needed. To pause without losing config, use `PATCH … active:false` instead.
What happens if my Dock webhook endpoint is down?
Dock retries with exponential backoff (1m, 5m, 30m, 2h, 12h, 24h) for up to 24 hours. After max retries, the delivery is marked failed in the webhook log. Re-send manually via the dashboard or POST `/api/webhooks/:id/deliveries/:deliveryId/redeliver`.
Can I filter which Dock events fire which webhook?
Per-webhook event subscription via the `events` array on POST/PATCH. Within an event kind, no built-in body-level filter; do per-event filtering in your handler (the payload includes `principal`, `workspace`, the changed object).
What's the rate limit on Dock webhook deliveries?
Plan-tier-scoped: Free 1k/mo, Pro 10k/mo, Scale 100k/mo. Per-org budget across all webhooks. Hitting the cap returns 402 on subsequent event triggers; existing deliveries continue but new ones queue.
How do I send Dock events to Slack?
Register a Slack incoming webhook URL (workflow apps work too) as the Dock webhook endpoint. Map Dock's payload shape into Slack's expected shape with a thin proxy (Cloudflare Worker, AWS Lambda) or use n8n / Zapier as the bridge for no-code routing.
Updated