All guides
Get started
The BLOM method
Agents
Developers
Administration
DocsDevelopers
Webhooks
BLOM tells a receiver you run when a task, a next action, a claim, or a closure changes. Each delivery is signed and carries no content.
In BLOMOrganizationConnect AgentWebhooks
What a webhook sends
A webhook is a signed POST that BLOM sends to an https address you run whenever something changes in your organization: a task captured, moved, or closed, a next action, a claim, a closure, or token use recorded. It carries identifiers, state names, and times, never content such as a title, a brief, or a note. It changes nothing in BLOM. Your receiver reads the details it needs through the public API.
Only an Administrator manages webhooks: a person with that role, or an agent's key with Admin access. Anyone else sees "Only an Administrator of this organization manages its webhooks and sees their deliveries." Each webhook has an owner, whoever added it, and is sent events only while its owner is an active Administrator.
Add a webhook
-
Open Webhooks
Open Organization, then Connect Agent, and select Webhooks in its Webhooks section.
-
Name the receiver
Select Add webhook. In Receiver URL, give an https address on a public domain name, such as
https://hooks.example.com/blom, on the standard port, with no user name, password, or#part. BLOM sends to no IP address or local name. Add a Description (optional); left empty, it's the receiver's host. -
Choose its events and product
Under Events, choose All events, which takes every type, including any type added to the catalog later, or Chosen events and select the ones you want. Under Product, keep All products, or choose one product to be sent only its events.
-
Store the signing secret
Select Add the webhook. BLOM shows the webhook's signing secret once. Select Copy the secret, put it where your receiver reads it, and then select I've stored it.
-
Send a test
Open the webhook from the list and select Send test. The test's delivery shows under Deliveries in the last 30 days, with each attempt's result.
Caution. Keep the secret out of repositories, tasks, and documents. If it's lost or exposed, rotate it.
How many webhooks an organization may have depends on its plan: see Pricing. From a terminal, the CLI adds one and prints its secret once. Its --url is the receiver's address, so BLOM's own comes from BLOM_URL or your sign-in:
node blom-cli.mjs webhooks create --url https://hooks.example.com/blom --events task.closed,claim.started
Events
| Type | In BLOM | Sent when | Its own fields |
|---|---|---|---|
task.captured | Task captured | A task is captured or opened. | homeItemId |
task.phase_changed | Task moved | A task moves to another phase. | fromPhaseId, toPhaseId, toPhaseLabel |
task.closed | Task closed | A task closes. | resolution: completed, duplicate, declined, or cancelled |
task.reopened | Task reopened | A closed task reopens. | None |
next_action.changed | Next action set | A task gets a new next action. | nextActionId, kind, and waitsOn: whom it waits on |
next_action.completed | Next action completed | A next action is completed. | nextActionId |
claim.started | Claim made | A person or an agent claims a next action. | claimId, nextActionId, holderActorId, state |
claim.ended | Claim ended | A claim ends. | claimId, nextActionId, endKind, such as completed or taken_over |
closure.returned | Closure returned | A session's closure is returned for a task. | closureId, claimId, status |
token_use.recorded | Token use recorded | A token-use report is recorded. | tokenUseId, source |
webhook.test | Test | You select Send test. | None |
Every event's data holds organizationId, endpointId, and eventId. The task events, the first nine, also hold changeId, the actor who made the change (its kind, person, agent, or service, and its id), taskId, taskKey, and productId. token_use.recorded holds changeId, then taskId, taskKey, and productId, which are null when the report names no task or product. It names no actor. Besides ids, states, and times, the only text an event carries is a phase's label, in toPhaseLabel.
The request
Each delivery is a POST with these headers and no others:
| Header | Value |
|---|---|
content-type | application/json |
user-agent | BLOM-Webhooks/1 |
webhook-id | The event's id, as in the body's eventId, the same on every attempt. A webhook-id you've seen before is a repeat. |
webhook-timestamp | When this attempt started, in Unix seconds. |
webhook-signature | v1, and a signature. For 24 hours after a rotation, two of them, separated by a space. |
The body is JSON with the event's type, the timestamp when the change was recorded, and its data. BLOM sends it on one line; it's spread out here to read:
{
"type": "task.closed",
"timestamp": "2026-11-02T14:05:09.120Z",
"data": {
"organizationId": "…",
"endpointId": "…",
"eventId": "…",
"changeId": "…",
"actor": { "kind": "agent", "id": "…" },
"taskId": "…",
"taskKey": "HL-12",
"productId": "…",
"resolution": "completed"
}
}
BLOM builds the body the same way on every attempt, so a retry or a redelivery carries the same bytes under the same webhook-id.
Verify the signature
BLOM signs each delivery as Standard Webhooks does, so a Standard Webhooks library checks it with your secret. To check it yourself:
-
Keep the body as received
Check the body's exact bytes, before you parse it. Parsing the JSON and writing it out again changes the bytes, and the signature with them.
-
Check the timestamp
Refuse a delivery whose
webhook-timestampis more than 5 minutes from your clock, as the libraries do. -
Make the key
Drop
whsec_from the start of the secret and decode the rest from base64. That gives the 32-byte key. -
Sign the content
Join the
webhook-id, thewebhook-timestamp, and the body with periods, asid.timestamp.body. Compute its HMAC-SHA256 with the key, and encode the result in base64. -
Compare
Split
webhook-signatureon spaces. Each part isv1,followed by a signature. The delivery is BLOM's when any one of them equals yours, compared in constant time.
In Node.js, for example:
import { createHmac, timingSafeEqual } from "node:crypto";
// secret: the webhook's signing secret, whsec_…
// headers: the request's headers, by lowercase name
// body: the request's body as text, exactly as received
export function isFromBlom(secret, headers, body) {
const id = headers["webhook-id"] ?? "";
const timestamp = headers["webhook-timestamp"] ?? "";
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!(age <= 300)) return false;
const key = Buffer.from(secret.slice("whsec_".length), "base64");
const expected = createHmac("sha256", key).update(`${id}.${timestamp}.${body}`).digest();
return (headers["webhook-signature"] ?? "").split(" ").some((part) => {
const [version, signature = ""] = part.split(",");
const given = Buffer.from(signature, "base64");
return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected);
});
}
Then answer with a 2xx status within 10 seconds, and do the work after.
Retries and failures
A delivery succeeds when your receiver answers with a 2xx status within 10 seconds. Anything else is a failed attempt: another status, no answer in time, a redirect, which BLOM doesn't follow, or an address BLOM refuses to send to when it connects.
After a failed attempt, BLOM tries again 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, and 12 hours later. The seventh failure, about 21 hours after the first, holds the delivery as Failed after 7 attempts. A test gets one attempt.
- BLOM sends a webhook's deliveries one at a time, oldest first. After any failed attempt, it sends that webhook nothing for 60 seconds.
- 50 failures in a row pause the webhook: Paused after 50 failed deliveries in a row.
- A 410 Gone pauses it at once: Paused: the receiver answered 410 Gone.
A webhook's page lists its deliveries of the last 30 days, newest first, 50 at a time. Open one to see each attempt's time, result, status, error, and duration, and the body sent. Redeliver sends it again now, under the same webhook-id, with a new series of attempts.
Pause, rotate, or delete
- Pause
- BLOM sends the webhook nothing, tests included, until you select Resume. What it was owed shows as Not sent: the endpoint was paused, and resuming doesn't send it. Redeliver any of it you still want.
- Rotate secret
- BLOM makes a new secret and shows it once. For 24 hours each delivery carries two signatures, the new secret's first, then the old one's, so you can move your receiver over. After that, it carries the new one alone.
- Delete
- BLOM sends nothing more and cancels what it still owed. Its record is kept, but it no longer shows on Webhooks.
- Edit settings
- Change the receiver URL, the description, the events, or the product, then select Save the settings.
If a webhook's owner stops being an Administrator, BLOM pauses it and says so. Resuming it makes you its owner.
From a terminal, the CLI's webhooks pause, resume, rotate-secret, delete, test, and deliveries each take the webhook's id, which webhooks list shows. Its op command reaches the rest, such as webhooks.updateEndpoint and webhooks.redeliver.
Related
Write to us, and we'll answer and add it here.