Browse all docs
API Reference

Webhooks API Reference

7 min read

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 HTTPS
  • events (required) — array of event types, or ["*"] for everything
  • project_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 ID
  • previous — present on *.updated and *.status_changed events, containing only the changed fields’ old values
  • actor — 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 2xx within 10 seconds — do heavy processing async and return 200 immediately
  • 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 with POST /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.

Was this helpful?