Before you start
- A tool that can send an HTTP POST to a URL — Bugsnag, Rollbar, a Datadog monitor, a form tool, your own script — and permission to add a webhook in it.
- An app in live mode: in qarunbook, App configuration → Settings → What this app is for → Tracking bugs in a live app. A monitoring tool cannot say which check failed.
- To be an admin of the app in qarunbook. The webhook is on every plan.
Connect an app, step by step
- In qarunbook
Open the app's webhook settings
Open the app, choose App configuration, then the Integrations tab, and choose the Incoming webhook tile under Where errors are caught.
- In qarunbook
Pick the payload, and turn it on
Under Payload, pick what the tool sends: qarunbook (native), Bugsnag, Rollbar, Datadog monitor or Custom mapping (see Presets).
Optionally pick the platforms under When the payload names no platform, mark it as affecting, and a priority under When the payload names no priority. Choose Turn on.
- In qarunbook
Copy the URL
The card now shows This app's webhook URL. Choose Copy.
- In qarunbook
Optionally, sign deliveries
If the tool can sign what it sends, under Signing choose Make a secret and copy it — it is not shown again — or paste the tool's own secret and choose Use it. From then on, unsigned deliveries are refused (see Signing).
With a secret set, you can also give a Notify URL (optional) to hear back when a bug is fixed (see Hearing back), and choose Save.
- In Your tool
Paste the URL into your tool
Add a webhook in the tool and paste the URL. Where that is, for the presets:
- Bugsnag: Project settings → Integrations and email → Data forwarding → Webhook.
- Rollbar: Settings → Notifications → Webhook.
- Datadog: Integrations → Webhooks → New, with the payload below; then mention
@webhook-namein a monitor's message.
Any other tool: wherever it keeps outgoing webhooks, with the method POST.
Check it works
A test bug in our own shape, with curl. Only title is needed; swap in your URL and one of the app's platform codes.
curl -X POST "https://qarunbook.com/api/integrations/webhook/<token>" \
-H "Content-Type: application/json" \
-d '{
"title": "Checkout button does nothing",
"description": "Tapping Pay shows a spinner forever.",
"platforms": ["IOS"],
"priority": "high",
"url": "https://shop.example.com/checkout",
"reporter": { "name": "Ada Lovelace", "email": "[email protected]" },
"external_id": "ticket-4812",
"link": "https://support.example.com/tickets/4812"
}'- In Your terminal
Send the test bug
Run the command above with the URL you copied.
- In Your terminal
Read the answer
A
201with the new issue's code and link:{"issue":{"id":"…","code":"QA-41","url":"…"}}. - In qarunbook
See the bug
The app has a new bug, Checkout button does nothing, reading Ada Lovelace via webhook, with the page and a link back to the ticket.
- In Your terminal
Send it again
While the issue is open, the same
external_idis not raised twice: the answer is200with"duplicate": trueand the existing issue. A body with no title is answered400 missing_title, and a wrong URL404. - In Your tool
Try it from your tool
Use the tool's own test button where it has one, and look for the bug on the app.
How it works
- Each live app can have a private URL, like
https://qarunbook.com/api/integrations/webhook/k3x9…. - Paste it into the tool as a webhook. Each POST it sends becomes an issue on the app, and the answer carries its code.
- The body can be in our own shape, or in the tool's: pick a preset for Bugsnag, Rollbar or a Datadog monitor, or type the paths to the fields you want.
- The same error sent again while its issue is open is not raised twice.
It is the same issue as one raised in the app, so everything else follows: it is filed in Linear, Jira, GitHub or GitLab if the app is connected, Slack and Microsoft Teams hear of it, and it counts towards Free's monthly allowance of issues.
If your code can set an Authorization header and you want checks, results and lists too, use the REST API instead. The webhook is for tools that can only POST to a URL.
Our own shape
Only title is needed. Everything else is optional:
title— the first line of the issue. Without one, the first line ofdescriptionis used.description— the rest of it.platforms— the platforms it affects: a list or a comma-separated string of the app's codes or names.iosorandroidwork when the app has one platform of that kind. Words that match nothing, likeproduction, are ignored, and the card's default applies.priority—urgent,high,mediumorlow. Severities are understood too:criticalandfatalare urgent,erroris high,warningmedium,infolow, and Datadog'sP1–P5and Rollbar's numeric levels map the same way. Anything else gets the card's default.url— the page it happened on, added to the issue's text.reporter—{ name, email? }, someone outside the team. The issue reads Ada Lovelace via webhook and the REST API returns her asreporter.external_id— the tool's own id for the problem, for dedupe.link— a link back to it in the tool, shown on the issue.httpandhttpslinks only.source— a short name for the tool, kept with the issue. Presets fill it in themselves.
The test above sends every one of them, and is answered:
HTTP/1.1 201 Created
{ "issue": { "id": "m1x…", "code": "QA-41", "url": "https://qarunbook.com/?app=…&bug=…" } }JSON is read whatever the Content-Type says, since some tools send text/plain. A form post works too, as its fields, or with the JSON in a payload field.
Presets
A preset knows where a tool keeps the title, the id, the link and the severity in its own webhook. Anything it does not find falls back to our shape, so the fields above still work alongside it. The card lists the paths each preset reads, with a link to the tool's docs.
Bugsnag
In Bugsnag, open the project's Project settings → Integrations and email → Data forwarding, add a Webhook, paste the URL, and choose which events notify it. The title is the error class and message; the error's id dedupes, its page in Bugsnag is the link, and its severity is the priority. See Bugsnag's webhook docs.
Rollbar
In Rollbar, open the project's Settings → Notifications, add the Webhook channel with the URL, and pick the rules — New item and Reactivated item are the useful ones. The item's title, id, link and level are read; every occurrence of one item dedupes onto one issue. See Rollbar's webhook docs.
Datadog monitors
In Datadog, go to Integrations → Webhooks, add a new webhook with the URL, and use this payload — the default one plus three fields. Then mention @webhook-qarunbook (or whatever you named it) in a monitor's message. The aggregation key is the same for an alert and its recovery, so they land on one issue. See Datadog's webhook docs.
{
"title": "$EVENT_TITLE",
"body": "$EVENT_MSG",
"id": "$ID",
"aggreg_key": "$AGGREG_KEY",
"link": "$LINK",
"priority": "$ALERT_PRIORITY"
}Any other payload
Choose Custom mapping and type where each field is — Title, Description, Link back, External id, Priority and Platform — as a dot path into the JSON. Lists take an index: items[0].title or items.0.title. Two more forms help with real payloads:
error.errorId|error.id— the first of these that has a value.{error.class}: {error.message}— a template. Placeholders are filled in, a line whose placeholders are all empty is left out, and stray separators at the ends are trimmed.
// A payload from some tool…
{
"event": "error.created",
"data": {
"error": { "id": "e_93", "message": "Payment failed", "level": "critical", "os": "Android" },
"permalink": "https://monitor.example.com/e_93"
}
}
// …and the paths that read it, typed into the card:
Title data.error.message
Description Level {data.error.level} on {data.error.os}
Link back data.permalink
External id data.error.id
Priority data.error.level
Platform data.error.osChange the preset or the paths on a webhook that is already on, then choose Save.
Dedupe
Error trackers send the same error more than once — a spike, a retry, a reopened item. When a delivery carries an external id and an issue from the same source with that id is still open, nothing new is raised: the answer is 200 with "duplicate": true and the existing issue. Once that issue is marked fixed, the next delivery raises a new one, because the error is back.
HTTP/1.1 200 OK
{ "duplicate": true, "issue": { "id": "m1x…", "code": "QA-41", "url": "https://qarunbook.com/?app=…&bug=…" } }Without an external id, every delivery is its own issue.
Signing
Anyone who has the URL can raise a bug — that is what lets a tool with no settings for keys use it. If the tool can sign what it sends, add a signing secret on the card: Make a secret, or paste the one your tool gave you and choose Use it. From then on a delivery must carry
X-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with the secret>and anything without it, or with the wrong one, is refused with 401 invalid_signature. Sign the exact bytes you send — re-encoding the JSON after signing changes them.
BODY='{"title":"Checkout button does nothing"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$QARUNBOOK_WEBHOOK_SECRET" | sed 's/^.* //')
curl -X POST "https://qarunbook.com/api/integrations/webhook/<token>" \
-H "Content-Type: application/json" \
-H "X-Signature: sha256=$SIG" \
-d "$BODY"import { createHmac } from "node:crypto";
const body = JSON.stringify({ title: "Checkout button does nothing" });
const signature = "sha256=" + createHmac("sha256", process.env.QARUNBOOK_WEBHOOK_SECRET).update(body).digest("hex");
await fetch(process.env.QARUNBOOK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json", "X-Signature": signature },
body, // exactly the string that was signed
});Make a new secret replaces it; Remove secret takes unsigned deliveries again and clears the notify URL.
Hearing back when it is fixed
Give the card a Notify URL, and when a bug that came in through the webhook is marked fixed — from any door: the app, MCP, the API, a merged pull request, Linear or Jira — or its fix is verified, we POST it there, signed with the same secret in the same header. Use it to resolve the error in the tool it came from. It is sent once, with a five-second timeout, and redirects are not followed. The notify URL must be public HTTPS, and needs a signing secret.
POST https://hooks.example.com/qarunbook
Content-Type: application/json
X-Qarunbook-Event: issue.fixed
X-Signature: sha256=…
{
"event": "issue.fixed",
"at": "2026-10-05T09:12:00.000Z",
"app": { "id": "…", "name": "Checkout" },
"issue": {
"id": "m1x…", "code": "QA-41", "url": "https://qarunbook.com/?app=…&bug=…",
"external_id": "5f3e9a", "source": "bugsnag", "link": "https://app.bugsnag.com/…",
"status": "fixed", "fixed_by": "Sam", "fixed_at": "…", "verified_by": null, "verified_at": null
}
}Answers
201— raised.{ issue: { id, code, url } }.200— already open, see dedupe.400 invalid_json— the body is not a JSON object.400 missing_title— nothing to call the issue.401 invalid_signature— the app has a signing secret and the signature is missing or wrong.402 limit_reached— the workspace has used this month's issues on Free.404 not_found— no webhook at that URL: it is off, was replaced, or never existed.409 not_live— the app is not in live mode.409 in_progress— the same external id is being raised by another delivery at this moment; retry.413 too_large— the body is over 1 MB.429 rate_limited— over 120 deliveries this hour to one app. Wait for the hour to turn.
Errors are { error: { code, message } }, the same as the REST API's.
Good to know
- The webhook only brings bugs in, and with a notify URL sends word back when they are fixed. It does not create checks or sections.
- The URL is the permission. Regenerate URL, then Make a new URL, makes a new one and stops the old one at once, for when it has ended up somewhere it should not be.
- Turn off refuses everything until you choose Turn on again; the URL stays the same.
- If the app goes back to testing, deliveries are refused with
409 not_liveuntil it is live again. - A bug that came in through the webhook shows a link back to it in the tool — Bugsnag, Rollbar, Datadog — with its external id.