Docs
Webhooks
A webhook is a URL you own that Bloom posts to when something happens in your workspace, so you find out without polling for it. Made from a workspace's Webhooks page, up to the number your plan allows.
Events
Every event Bloom can send. Some are registered but not sent yet, marked below, because they depend on a checkout Bloom has not opened.
| Event | Group | Fires when |
|---|---|---|
| license.issued | Licences | Somebody got a key: you issued it, the API did, or a sale did. |
| license.validated | Licences | Software started up and its key was accepted. The busiest event by far. |
| license.refused | Licences | A key was rejected, expired, revoked, on a machine too many, or blacklisted. |
| license.suspended | Licences | Paused, by you or by a rule. It can be turned back on. |
| license.revoked | Licences | Ended for good. The key will never validate again. |
| license.expired | Licences | Its time was up rather than taken away. Usually where a renewal belongs. |
| store.viewed | Buyers | A visitor read one of your listings in the store. |
| checkout.clicked | Buyers | They clicked Buy on a package and were sent to your Discord to open a ticket. |
| release.downloaded | Buyers | Somebody followed the download link on one of your releases. |
| product.published | Products | It went live in the store, or came back after being unpublished. |
| release.published | Products | A new version was published for a product. |
| store.approved | Products | Bloom reviewed the listing and it is now visible in the public store. |
| plan.started | Money | Somebody put this workspace on a paid plan for the first time. |
| plan.renewed | Money | The monthly charge went through and the plan runs for another month. |
| plan.ending | Money | Cancelled, or the card stopped working. The plan runs to the end of what was paid for. |
| plan.expired | Money | The date passed and the workspace is on Free again. Nothing in it was removed. |
| plan.reminder | Money | Somebody was emailed that this workspace’s plan ends soon. |
| member.signed_in | The workspace | A member of this workspace signed in. |
Registered, not sent yet
payment.startedA buyer reached checkout, not yet paid. Nothing emits this until Bloom takes payments.payment.completedPaid and cleared, with the licence issued off the back of it. Waiting on Bloom’s checkout.
Payload shape
Every delivery is a POST with a JSON body shaped the same way regardless of event:
{
"event": "license.issued",
"sentAt": "2026-01-01T00:00:00.000Z",
"data": { /* event-specific fields */ }
} Headers on every delivery: X-Bloom-Event (the event name, matching the body) and X-Bloom-Signature (the signature, see below).
Verifying the signature
X-Bloom-Signature is sha256=<hex>, an HMAC-SHA256 of the exact raw request body using the webhook's own secret (shown once when you view the webhook's secret in the dashboard, prefixed whsec_). Compare it in constant time, not with ===, the same way Bloom's own code does.
const crypto = require('node:crypto');
function isFromBloom(rawBody, signatureHeader, secret) {
// Header looks like "sha256=<hex>".
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// In your route handler, before you parse the body as JSON:
if (!isFromBloom(rawBody, req.headers['x-bloom-signature'], webhookSecret)) {
res.statusCode = 401;
return res.end('bad signature');
}Retries and failure
A delivery is not retried on failure. Consecutive failures are counted, and after 10 in a row the webhook is switched off automatically; turning it back on from the dashboard clears the count. Redirects are never followed: an endpoint that answers 3xx is treated as a failure.
Where a URL can point
A webhook URL must resolve to a public address, both when it is saved and again every time it is sent to. Private and internal addresses are refused, so a URL that answers publicly today cannot be pointed at something inside Bloom's own network tomorrow.