Webhooks API Reference
Webhooks push events to your server the moment they happen, eliminating polling. This reference covers managing subscriptions via the API, every event type, payload structure, signature verification, and delivery semantics. Managing webhooks requires the write:webhooks scope.
Create a Webhook Subscription
Endpoint
POST /v1/webhooks
Request
curl -X POST https://api.loomstack.com/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/loomstack",
"events": ["task.created", "task.status_changed"],
"project_ids": ["proj_884"],
"active": true
}'
url(required) — must be HTTPSevents(required) — array of event types, or["*"]for everythingproject_ids— omit to receive events from all projects
Response
{
"success": true,
"data": {
"id": "wh_310",
"url": "https://example.com/hooks/loomstack",
"events": ["task.created", "task.status_changed"],
"secret": "whsec_9f2ac81d...",
"active": true
}
}
The secret is shown only once — store it for signature verification.
Manage subscriptions with GET /v1/webhooks, PATCH /v1/webhooks/{webhookId}, and DELETE /v1/webhooks/{webhookId}.
Event Types
Tasks: task.created, task.updated, task.deleted, task.status_changed, task.assigned, task.due_soon (fires 24h before due date)
Comments: comment.created, comment.deleted
Projects: project.created, project.updated, project.archived, project.deleted
Members: member.invited, member.joined, member.deactivated
Payload Structure
Every delivery is a POST with a common envelope:
{
"id": "evt_58201",
"event": "task.status_changed",
"created_at": "2026-09-25T14:02:11Z",
"workspace_id": "ws_22",
"data": {
"task": {
"id": "task_789",
"project_id": "proj_884",
"title": "Fix login bug",
"state": "done",
"assignee_id": "user_456"
},
"previous": { "state": "in_progress" },
"actor": { "id": "user_456", "name": "John Doe" }
}
}
id— unique event ID; use it for deduplication, since retries re-send the same IDprevious— present on*.updatedand*.status_changedevents, containing only the changed fields’ old valuesactor— the user (or API key) that caused the event
Verifying Signatures
Each request carries an X-Loomstack-Signature header:
X-Loomstack-Signature: t=1758808931,v1=5257a869e7...
Compute HMAC-SHA256 over {t}.{raw_body} using your whsec_ secret and compare to v1 with a constant-time comparison. Reject requests where t is older than 5 minutes to prevent replay attacks.
const crypto = require("crypto");
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map(p => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
Delivery and Retries
- Your endpoint must respond with a
2xxwithin 10 seconds — do heavy processing async and return200immediately - Failed deliveries retry up to 5 times with exponential backoff (1m, 5m, 30m, 2h, 12h)
- After 20 consecutive failures across events, the subscription is auto-disabled (
active: false) and the key owner is emailed - Inspect history via
GET /v1/webhooks/{webhookId}/deliveries, and re-send any delivery withPOST /v1/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver
Summary
Create subscriptions with POST /v1/webhooks, scoping by event type and project. Verify every request’s X-Loomstack-Signature with your whsec_ secret, dedupe on the event id, and respond 200 fast — retries and the delivery log handle transient failures, but 20 straight failures disables the subscription.