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#
| Event | Fires when |
|---|---|
run.completed | A test run finished. The payload carries the run's result data. |
run.failed | A test run could not complete (e.g. the site was unreachable or login failed). |
pr.check.completed | A 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>
tis the Unix timestamp (in seconds) that was signed.v1isHMAC-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.
Related#
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.