Webhook Payloads
Receive real-time notifications when issues are created, resolved, reopened, or receive new events.
Verifying signatures
Every webhook request includes an HMAC-SHA256 signature so your server can verify that the request genuinely came from booboo.dev and wasn't tampered with. Always verify signatures in production.
Signature headers
| Header | Description |
|---|---|
X-Booboo-Signature | HMAC-SHA256 hex digest of the raw request body, using your webhook secret as the key |
X-Booboo-Timestamp | Unix timestamp (seconds) when the request was sent |
X-Booboo-Event | Event type: issue_created, issue_resolved, issue_reopened, event_received, or test |
Verification steps
- Read the raw request body as bytes (do not parse JSON first)
- Compute
HMAC-SHA256(body, your_webhook_secret) - Compare the result to the
X-Booboo-Signatureheader using a constant-time comparison - If they don't match, reject the request with a 401
Python
import hashlib
import hmac
import json
def verify_webhook(body: bytes, secret: str, signature: str) -> bool:
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature) Node.js
import crypto from "node:crypto";
function verifyWebhook(body, secret, signature) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);
} Event types
All issue event payloads share the same structure. The event_type field indicates which event triggered the webhook.
| Field | Description |
|---|---|
event_type | One of the event types listed below |
issue.id | Display ID in PREFIX-NUMBER format (e.g. BACK-42) |
issue.title | Exception type and message |
issue.culprit | Source location of the error (file and function) |
issue.status | Current issue status: open, resolved, or ignored |
issue.first_seen | ISO 8601 timestamp of the first occurrence |
issue.last_seen | ISO 8601 timestamp of the most recent occurrence |
issue.url | Direct link to the issue in the booboo.dev dashboard |
issue_created
Sent when a new issue is detected (first occurrence of a unique error fingerprint).
{
"event_type": "issue_created",
"issue": {
"id": "BACK-42",
"title": "ZeroDivisionError: division by zero",
"culprit": "app/utils.py in safe_divide",
"status": "open",
"first_seen": "2026-02-18T12:34:56.789Z",
"last_seen": "2026-02-18T12:34:56.789Z",
"url": "https://app.booboo.dev/my-org/projects/backend/issues/42"
},
"project": {
"name": "Backend",
"slug": "backend"
},
"organization": {
"name": "My Org",
"slug": "my-org"
}
} issue_resolved
Sent when a user marks an issue as resolved in the dashboard.
{
"event_type": "issue_resolved",
"issue": {
"id": "BACK-42",
"title": "ZeroDivisionError: division by zero",
"culprit": "app/utils.py in safe_divide",
"status": "resolved",
"first_seen": "2026-02-18T12:34:56.789Z",
"last_seen": "2026-02-18T14:20:00.000Z",
"url": "https://app.booboo.dev/my-org/projects/backend/issues/42"
},
"project": {
"name": "Backend",
"slug": "backend"
},
"organization": {
"name": "My Org",
"slug": "my-org"
}
} issue_reopened
Sent when a resolved issue receives a new event (regression detected) or is manually reopened by a user.
{
"event_type": "issue_reopened",
"issue": {
"id": "BACK-42",
"title": "ZeroDivisionError: division by zero",
"culprit": "app/utils.py in safe_divide",
"status": "open",
"first_seen": "2026-02-18T12:34:56.789Z",
"last_seen": "2026-02-19T09:15:30.000Z",
"url": "https://app.booboo.dev/my-org/projects/backend/issues/42"
},
"project": {
"name": "Backend",
"slug": "backend"
},
"organization": {
"name": "My Org",
"slug": "my-org"
}
} event_received
Sent when a new event arrives for an existing open or ignored issue. Throttled to at most 1 notification per 5 minutes per issue per routing rule to avoid flooding.
{
"event_type": "event_received",
"issue": {
"id": "BACK-42",
"title": "ZeroDivisionError: division by zero",
"culprit": "app/utils.py in safe_divide",
"status": "open",
"first_seen": "2026-02-18T12:34:56.789Z",
"last_seen": "2026-02-18T15:45:12.000Z",
"url": "https://app.booboo.dev/my-org/projects/backend/issues/42"
},
"project": {
"name": "Backend",
"slug": "backend"
},
"organization": {
"name": "My Org",
"slug": "my-org"
}
} test
Sent when you click the Test button in organization settings. Use this to verify your endpoint is reachable.
{
"event_type": "test",
"message": "This is a test webhook from booboo.dev",
"integration": {
"id": "a1b2c3d4-...",
"name": "Slack #errors"
}
} Full examples
Express (Node.js)
app.post("/webhooks/booboo", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-booboo-signature"];
if (!verifyWebhook(req.body, process.env.BOOBOO_WEBHOOK_SECRET, signature)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body);
switch (event.event_type) {
case "issue_created":
console.log("New issue:", event.issue.id, event.issue.title);
break;
case "issue_resolved":
console.log("Resolved:", event.issue.id);
break;
case "issue_reopened":
console.log("Reopened:", event.issue.id);
break;
case "event_received":
console.log("New event for:", event.issue.id);
break;
}
res.sendStatus(200);
}); Flask (Python)
@app.route("/webhooks/booboo", methods=["POST"])
def booboo_webhook():
signature = request.headers.get("X-Booboo-Signature", "")
if not verify_webhook(request.data, WEBHOOK_SECRET, signature):
return "Invalid signature", 401
event = request.get_json()
issue = event.get("issue", {})
match event["event_type"]:
case "issue_created":
print(f"New issue: {issue['id']} {issue['title']}")
case "issue_resolved":
print(f"Resolved: {issue['id']}")
case "issue_reopened":
print(f"Reopened: {issue['id']}")
case "event_received":
print(f"New event for: {issue['id']}")
return "", 200 Delivery behavior
- Webhooks are sent with a 10-second timeout
- Your endpoint must return a 2xx status code — any 4xx or 5xx is treated as a failure
- Redirects are not followed to prevent SSRF attacks
- In production, webhook URLs must use HTTPS and must not resolve to private/internal IP addresses
- There are currently no automatic retries — if delivery fails, the webhook is dropped
Security notes
- Your webhook secret is shown once at creation time. Store it securely (e.g. in environment variables)
- If your secret is compromised, delete the integration and create a new one
- Always verify the
X-Booboo-Signatureheader before processing webhook payloads - Consider checking the
X-Booboo-Timestampheader to reject stale requests (e.g. older than 5 minutes) to prevent replay attacks