// 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

1

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);
2

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
3

Verify signatures

Always verify the HMAC signature before processing a webhook. See Signature Verification below.

4

Test

Click the Test button in Settings to send a synthetic event. Check your endpoint receives it with a valid signature.

Events reference

Items

EventTriggerPayload includes
item.createdNew item added to a boardid, name, boardId, groupId, createdById
item.updatedItem fields modifiedid, name, changedFields
item.deletedItem permanently deletedid, name, boardId
item.status_changedStatus column value changedid, name, oldStatus, newStatus

KPIs

EventTriggerPayload includes
kpi.value_enteredNew KPI measurement recordedkpiDefinitionId, name, value, date
kpi.threshold_breachedValue crosses red/amber thresholdname, value, alertStatus, thresholds

Actions

EventTriggerPayload includes
action.createdNew action item createdid, title, priority, category, boardId
action.updatedAction status/assignment changedid, title, changedFields
action.escalatedAction escalated T1 to T2 to T3id, title, fromTier, toTier, reason
action.completedAction marked as doneid, title, completedAt

Other

EventTriggerPayload includes
comment.createdNew comment on an itemitemId, content, authorId
member.addedUser added to organizationuserId, role
member.removedUser removed from organizationuserId
checklist.completedAll checklist items marked donechecklistId, name, completedBy
audit.completedAudit/inspection finalizedauditId, 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:

AttemptDelayTotal elapsed
1st deliveryImmediate0s
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