Webhooks

Receive signed real-time HTTP notifications when records, comments, tags, and other entities change in Blue.


Webhooks deliver real-time HTTP notifications when events occur in your workspaces — a record is created, a comment is posted, a tag is removed, and more. When a subscribed event fires, Blue sends a signed POST request to your endpoint with the event type and the entity’s before/after state.

Webhooks are Webhook objects in the API, owned by the user who created them. Each webhook subscribes to a set of event types and is scoped to a set of workspaces (Project objects).

Operations

OperationMutation / QueryDescription
Create a webhookcreateWebhookRegister an endpoint and choose which events it receives.
List webhookswebhooks / webhookPage through your webhooks or fetch one by ID.
Update a webhookupdateWebhookChange URL, events, scope, or enable/disable a webhook.
Disable a webhookdisableWebhookStop delivery and mark a webhook unhealthy without deleting it.
Delete a webhookdeleteWebhookPermanently remove a webhook.

Authenticating webhook requests

Manage webhooks with your personal access token headers. createWebhook additionally requires X-Bloo-Company-ID because the webhook count is enforced per organization; the other webhook operations are user-scoped.

curl -X POST https://api.blue.cc/graphql \
  -H "Content-Type: application/json" \
  -H "X-Bloo-Token-ID: YOUR_TOKEN_ID" \
  -H "X-Bloo-Token-Secret: YOUR_TOKEN_SECRET" \
  -H "X-Bloo-Company-ID: YOUR_COMPANY_ID" \
  -d '{"query": "query { webhooks { items { id url status enabled } } }"}'

Delivery and scoping

Each event is enqueued with a 5-second delay before the first delivery attempt. Blue POSTs the payload to your URL with a 7-second request timeout.

A webhook only receives an event when all of the following hold:

  • The webhook is enabled.
  • Its events list contains the event type — or the list is empty, which subscribes it to every event.
  • Its projectIds list contains the event’s workspace — or the list is empty, which scopes it to all workspaces.
  • The webhook’s creator is currently a member of the event’s workspace. Project scoping is additionally gated by live membership, so removing the creator from a workspace silently stops delivery for events from it.

If a delivery attempt fails (non-2xx response, timeout, or connection error), BullMQ retries with backoff — 5 attempts by default. After all retries are exhausted, the webhook is automatically disabled (enabled: false, status: UNHEALTHY) and its creator is emailed. This is what HEALTHY versus UNHEALTHY reflects: a healthy webhook is delivering; an unhealthy one was auto-disabled (or disabled manually) and must be re-enabled via updateWebhook.

Webhook payload

Blue sends a JSON body with this exact shape:

{
  "event": "TODO_DONE_STATUS_UPDATED",
  "webhook": {
    "id": "clm4n8qwx000008l0g4oxdqn7",
    "uid": "wh_8f3a2c1e",
    "url": "https://example.com/webhooks/blue",
    "status": "HEALTHY",
    "enabled": true,
    "metadata": { "events": [], "projectIds": [] }
  },
  "previousValue": { "id": "todo_123", "title": "Draft proposal", "done": false },
  "currentValue": { "id": "todo_123", "title": "Draft proposal", "done": true }
}
FieldDescription
eventThe WebhookEvent value that fired (e.g. TODO_CREATED).
webhookThe webhook’s own configuration. The secret is stripped from this object — it never appears in a delivery.
previousValueThe entity’s state before the change. null for create events.
currentValueThe entity’s state after the change. null for delete events. The shape matches the underlying entity (record, comment, tag, etc.) with its related rows expanded.

The delivered event may also be WEBHOOK_HEALTH_CHECK, sent when a webhook is re-enabled to verify reachability. That value is not part of the WebhookEvent enum and cannot be subscribed to — it is delivery-only, so handle unknown event strings defensively.

Verifying signatures

Every delivery includes an X-Signature header: the HMAC-SHA256 of the raw request body, keyed by the webhook’s secret (the value returned once from createWebhook), encoded as a hex string.

Hash the raw bytes, not a re-serialized object

Compute the HMAC over the exact bytes you received, before parsing. If you JSON.parse the body and re-stringify it, key ordering and formatting may differ from what Blue signed and the signature will not match.

const crypto = require('crypto')

// `rawBody` is the unparsed request body string (e.g. from a raw body parser).
function verifySignature(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
}

Webhook events

Subscribe to these WebhookEvent values via the events array on createWebhook or updateWebhook. An empty array subscribes to all of them.

Record events

EventDescription
TODO_CREATEDA record was created.
TODO_DELETEDA record was deleted.
TODO_MOVEDA record was moved to a different list.
TODO_NAME_CHANGEDA record’s title was changed.
TODO_DONE_STATUS_UPDATEDA record was marked done or undone.
TODO_DUE_DATE_ADDEDA due date was added to a record.
TODO_DUE_DATE_UPDATEDA record’s due date was changed.
TODO_DUE_DATE_REMOVEDA due date was removed from a record.
TODO_ASSIGNEE_ADDEDAn assignee was added to a record.
TODO_ASSIGNEE_REMOVEDAn assignee was removed from a record.
TODO_TAG_ADDEDA tag was added to a record.
TODO_TAG_REMOVEDA tag was removed from a record.
TODO_CUSTOM_FIELD_UPDATEDA custom field value changed on a record.

Checklist events

EventDescription
TODO_CHECKLIST_CREATEDA checklist was created on a record.
TODO_CHECKLIST_NAME_CHANGEDA checklist was renamed.
TODO_CHECKLIST_DELETEDA checklist was deleted.
TODO_CHECKLIST_ITEM_CREATEDA checklist item was created.
TODO_CHECKLIST_ITEM_NAME_CHANGEDA checklist item was renamed.
TODO_CHECKLIST_ITEM_DELETEDA checklist item was deleted.
TODO_CHECKLIST_ITEM_DUE_DATE_ADDEDA due date was added to a checklist item.
TODO_CHECKLIST_ITEM_DUE_DATE_UPDATEDA checklist item’s due date was changed.
TODO_CHECKLIST_ITEM_DUE_DATE_REMOVEDA due date was removed from a checklist item.
TODO_CHECKLIST_ITEM_ASSIGNEE_ADDEDAn assignee was added to a checklist item.
TODO_CHECKLIST_ITEM_ASSIGNEE_REMOVEDAn assignee was removed from a checklist item.
TODO_CHECKLIST_ITEM_DONE_STATUS_UPDATEDA checklist item was marked done or undone.

List events

EventDescription
TODO_LIST_CREATEDA list was created in a workspace.
TODO_LIST_DELETEDA list was deleted.
TODO_LIST_NAME_CHANGEDA list was renamed.

Custom field events

EventDescription
CUSTOM_FIELD_CREATEDA custom field was created.
CUSTOM_FIELD_DELETEDA custom field was deleted.
CUSTOM_FIELD_UPDATEDA custom field definition was updated.

Tag events

EventDescription
TAG_CREATEDA tag was created.
TAG_DELETEDA tag was deleted.
TAG_UPDATEDA tag was updated.

Comment events

EventDescription
COMMENT_CREATEDA comment was created.
COMMENT_DELETEDA comment was deleted.
COMMENT_UPDATEDA comment was updated.

The Webhook type

FieldTypeDescription
idID!Unique identifier.
uidString!Short public identifier.
nameStringHuman-readable label, or null.
urlString!The endpoint receiving events.
secretStringHMAC signing secret. Returned only from createWebhook; null on every other read.
statusWebhookStatusType!HEALTHY or UNHEALTHY.
events[WebhookEvent!]Subscribed event types. An empty array means all events.
projectIds[String!]Scoped workspace IDs. An empty array means all your workspaces.
enabledBooleanWhether the webhook is delivering.
metadataJSONStored scoping metadata (events and projectIds).
createdAtDateTime!Creation timestamp.
updatedAtDateTime!Last-update timestamp.

WebhookStatusType is HEALTHY or UNHEALTHY.