Webhooks
Receive real-time HTTP notifications when events happen in ProBeya. HMAC-signed, auto-retried, pharma-ready.
// Your webhook endpoint receives this when OEE drops below threshold
{
"event": "kpi.threshold_breached",
"timestamp": "2026-04-13T06:00:00.000Z",
"organizationId": "clx_org_acme",
"data": {
"kpiDefinitionId": "clx_kpi_oee_line3",
"name": "OEE — Packaging Line 3",
"category": "D",
"value": 72.5,
"unit": "%",
"alertStatus": "red",
"direction": "higher_is_better",
"thresholds": { "red": 70, "amber": 80, "green": 85 }
}
}
Webhooks push events to your systems as they happen — no polling. Every payload is signed with HMAC-SHA256, retried on failure, and includes the full event context your integration needs.
Setup
Create a webhook endpoint
Build an HTTPS endpoint that accepts POST requests. Here is a minimal Express example:
import express from "express";
const app = express();
app.post("/probeya-webhook", express.json(), (req, res) => {
console.log("Event:", req.body.event, req.body.data);
res.status(200).send("OK");
});
app.listen(3000);
Register in ProBeya
Go to Settings > Webhooks > Create Webhook.
- URL: Your endpoint (must be HTTPS in production)
- Events: Select which events trigger deliveries
- Secret: Auto-generated — copy it immediately
Verify signatures
Always verify the HMAC signature before processing a webhook. See Signature Verification below.
Test
Click the Test button in Settings to send a synthetic event. Check your endpoint receives it with a valid signature.
Events reference
Items
| Event | Trigger | Payload includes |
|---|---|---|
item.created | New item added to a board | id, name, boardId, groupId, createdById |
item.updated | Item fields modified | id, name, changedFields |
item.deleted | Item permanently deleted | id, name, boardId |
item.status_changed | Status column value changed | id, name, oldStatus, newStatus |
KPIs
| Event | Trigger | Payload includes |
|---|---|---|
kpi.value_entered | New KPI measurement recorded | kpiDefinitionId, name, value, date |
kpi.threshold_breached | Value crosses red/amber threshold | name, value, alertStatus, thresholds |
Actions
| Event | Trigger | Payload includes |
|---|---|---|
action.created | New action item created | id, title, priority, category, boardId |
action.updated | Action status/assignment changed | id, title, changedFields |
action.escalated | Action escalated T1 to T2 to T3 | id, title, fromTier, toTier, reason |
action.completed | Action marked as done | id, title, completedAt |
Other
| Event | Trigger | Payload includes |
|---|---|---|
comment.created | New comment on an item | itemId, content, authorId |
member.added | User added to organization | userId, role |
member.removed | User removed from organization | userId |
checklist.completed | All checklist items marked done | checklistId, name, completedBy |
audit.completed | Audit/inspection finalized | auditId, name, score, completedBy |
Payload format
Every webhook delivery follows the same envelope structure:
{
"event": "action.escalated",
"timestamp": "2026-04-13T14:30:00.000Z",
"organizationId": "clx_org_acme",
"actorId": "clx_user_jdoe",
"data": {
"id": "clx_act_batch_hold",
"title": "Investigate root cause of yield drop — Batch BX-2026-0412",
"priority": "high",
"fromTier": 1,
"toTier": 2,
"reason": "Unresolved after 72 hours. OEE impact confirmed at -12.5%.",
"category": "Q",
"boardId": "clx_board_quality",
"responsibleId": "clx_user_quality_mgr"
}
}
Signature verification
Every webhook request includes these headers:
X-ProBeya-Signature: sha256=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-ProBeya-Event: action.escalated
Content-Type: application/json
User-Agent: ProBeya-Cloud-Webhook/1.0
Always verify the signature. Without verification, an attacker could send fake events to your endpoint.
import crypto from "crypto";
function verifyWebhookSignature(rawBody, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(`sha256=${expected}`),
Buffer.from(signature)
);
}
// Express middleware
app.post(
"/probeya-webhook",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-probeya-signature"];
if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body);
switch (event.event) {
case "kpi.threshold_breached":
notifySlack(`KPI Alert: ${event.data.name} at ${event.data.value}${event.data.unit}`);
break;
case "action.escalated":
createJiraTicket(event.data);
break;
}
res.status(200).send("OK");
}
);
Use express.raw() (not express.json()) when verifying signatures. The signature is computed over the raw bytes. Parsing and re-serializing the JSON changes the byte representation and breaks verification.
Retry policy
If your endpoint returns a non-2xx status or times out (5-second timeout), ProBeya retries:
| Attempt | Delay | Total elapsed |
|---|---|---|
| 1st delivery | Immediate | 0s |
| 1st retry | ~1 second | ~1s |
| 2nd retry | ~2 seconds | ~3s |
| 3rd retry | ~4 seconds | ~7s |
After 3 failed attempts for a single event, that delivery is abandoned.
After 10 consecutive failures across any events, the webhook is automatically disabled. You will see a warning in Settings > Webhooks. Re-enabling resets the failure counter.
Best practices
Example: Slack alert for KPI breaches
A complete integration that sends Slack messages when KPIs cross thresholds:
import express from "express";
import crypto from "crypto";
const app = express();
const WEBHOOK_SECRET = process.env.PROBEYA_WEBHOOK_SECRET;
const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL;
app.post("/probeya-webhook", express.raw({ type: "application/json" }), async (req, res) => {
// 1. Verify signature
const signature = req.headers["x-probeya-signature"];
const expected = crypto.createHmac("sha256", WEBHOOK_SECRET).update(req.body).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(`sha256=${expected}`), Buffer.from(signature))) {
return res.status(401).send("Invalid signature");
}
res.status(200).send("OK");
// 2. Process event
const event = JSON.parse(req.body);
if (event.event === "kpi.threshold_breached") {
const { name, value, unit, alertStatus, category } = event.data;
await fetch(SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `${alertStatus === "red" ? ":red_circle:" : ":large_orange_circle:"} *${name}* is at ${value}${unit} (${category} pillar)`,
}),
});
}
});
app.listen(3000);
Next steps
Was this page helpful?