Webhook events

BugBrain webhook events reference — the run.completed, run.failed, and pr.check.completed event types, the payload shape, and how to verify the X-BugBrain-Signature HMAC-SHA256 signature with replay protection.

BugBrain can POST a signed JSON payload to your endpoint when key events happen. This page lists the event types, the payload shape, and how to verify a delivery. To register and manage webhooks, see Webhooks.

Event types#

EventFires when
run.completedA test run finished. The payload carries the run's result data.
run.failedA test run could not complete (e.g. the site was unreachable or login failed).
pr.check.completedA PR-automation check finished. The payload carries the pull request, repo, conclusion, impacted areas, and an issue summary.

A webhook subscribes to one or more of these events; only subscribed events are delivered.

Payload shape#

Each delivery is a JSON body containing the event type plus the relevant data for that event (the run for run events, or the PR check details for pr.check.completed). A typical run-event body looks like:

{
  "event": "run.completed",
  "data": {
    "runId": "…",
    "projectId": "…",
    "status": "COMPLETED",
    "issuesFound": 3
  }
}

Treat fields defensively

The exact field set is deployment-specific and may grow over time. Read by key, ignore unknown fields, and don't assume a fixed field order — that keeps your receiver forward-compatible.

Verifying the signature#

Every delivery includes the header:

X-BugBrain-Signature: t=<timestamp>, v1=<hmac>
  • t is the Unix timestamp (in seconds) that was signed.
  • v1 is HMAC-SHA256(secret, "<t>.<rawBody>"), hex-encoded.

To verify, recompute the HMAC over "<t>.<rawBody>" with your signing secret and compare it to v1 using a constant-time comparison. The signed timestamp gives replay protection: reject the request if t is outside your tolerance window (a few minutes is typical).

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = header.split(",").map((p) => p.trim());
  const t = Number(parts.find((p) => p.startsWith("t="))?.slice(2));
  const sig = parts.find((p) => p.startsWith("v1="))?.slice(3);
  if (!Number.isFinite(t) || !sig) return false;
  // Replay protection: reject stale timestamps.
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSec) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(sig, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}

Verify over the raw body

Sign and verify against the exact raw request bytes, before any JSON parsing or re-serialization. Re-stringifying the body can change whitespace or key order and break the signature.

The signing secret#

When you create a webhook, BugBrain returns a signing secret that starts with whsec_. It is shown once and never retrievable again — store it securely at creation time. If you lose it, rotate the webhook to get a new one.

Delivery and retries#

  • Your endpoint should respond with a 2xx status quickly; do slow work asynchronously.
  • A non-2xx response is retried with exponential backoff.
  • A destination that is disabled or fails a safety check (an unsafe or private URL) is not retried.

Frequently asked questions

Which events can BugBrain send?

Three event types — run.completed and run.failed for test runs, and pr.check.completed for a finished PR-automation check. You choose which events each webhook subscribes to.

How is a webhook signed?

Every delivery carries an X-BugBrain-Signature header of the form "t=<timestamp>, v1=<hmac>". The v1 value is an HMAC-SHA256 of "<timestamp>.<raw-body>" using your signing secret. The signed timestamp provides replay protection.

Where do I get the signing secret?

The signing secret (it starts with whsec_) is shown only once, when you create the webhook. Store it then — it can't be retrieved again later. If you lose it, rotate the webhook.

What happens if my endpoint is down?

Non-2xx responses are retried with exponential backoff. A permanently unsafe or disabled destination is not retried. Respond 2xx quickly and do the heavy work asynchronously.