Webhooks
Send issue, threshold and silence events to Slack, an incident tool or your own endpoint, signed so you can verify them.
A webhook posts JSON to a URL you choose when something happens in an application. Add one under Settings → Issues → Webhooks. Admins and owners can manage webhooks.
Create a webhook#
- Give it a name and the URL to post to (
httporhttps). - Choose its events. All of them are selected by default.
- Save it. Raccoon shows the signing secret once, so copy it then.
Send test posts a test event right away and shows the response. Each webhook's page logs every delivery with its status code, payload and response, filtered by Successful or Failed. The webhook list shows a warning when the last delivery failed.
Events#
| Event | Sent when |
|---|---|
issue.created |
An issue opens. A new performance issue also sends threshold.exceeded. |
issue.reopened |
A resolved issue happens again. |
issue.resolved |
An issue is resolved, by anyone, in bulk, through the MCP server or on deploy. |
issue.assigned |
An issue is assigned to someone. |
threshold.exceeded |
A route, job, command or scheduled task goes over its threshold. |
environment.silent |
An environment with a silence alert stops sending events. |
environment.recovered |
That environment sends events again. |
Payload#
Every delivery is a POST with a JSON body:
{
"id": "0c3f7c1e-2f3a-4c1b-9d2e-5b6a7c8d9e0f",
"event": "issue.created",
"created_at": "2026-11-03T09:41:00Z",
"data": {
"application": { "id": "9e5c1a52-8f0e-4b8e-a1c4-2f6d3b7e9a10", "name": "Shop" },
"environment": { "id": "9e5c1a52-93b1-4d27-b8f5-6a0c4e2d1f37", "name": "Production" },
"issue": {
"id": 81,
"ref": 12,
"type": "exception",
"issueable_type": null,
"group": "5d1c...",
"title": "TypeError",
"subtitle": "Cannot read properties of undefined (reading 'total')",
"status": "open",
"priority": 0,
"occurrences": 1,
"first_seen_at": "2026-11-03T09:40:58Z",
"last_seen_at": "2026-11-03T09:40:58Z",
"url": "https://raccoon.watch/environments/9e5c1a52-93b1-4d27-b8f5-6a0c4e2d1f37/issues/12"
}
}
}
issue.assignedaddsassigneewithid,nameandemail.threshold.exceededaddsthresholdwithtype,name,threshold_msandp95_ms.environment.silentandenvironment.recoveredhaveapplication,environment(withsilence_alert_minutes),silent_since,recovered_at(null while silent) andurl, and noissue.
The request also has the headers X-Raccoon-Event, X-Raccoon-Signature and User-Agent: Raccoon-Webhooks/1.0.
Verify the signature#
X-Raccoon-Signature is sha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with the webhook's secret. Compute it over the body exactly as received, before parsing the JSON, and compare in constant time:
import { createHmac, timingSafeEqual } from 'node:crypto';
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
const valid = expected.length === signature.length && timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
$valid = hash_equals('sha256='.hash_hmac('sha256', $request->getContent(), $secret), $request->header('X-Raccoon-Signature'));
Rotate secret on the webhook's page makes a new secret. The old one stops working immediately.
Retries#
Raccoon waits up to 10 seconds for a response. A delivery fails when there's no response or the status is 400 or above. A failed delivery is retried three more times, after 10 seconds, 1 minute and 5 minutes. Disabling or deleting the webhook cancels pending retries.