Incidents and webhooks
Incidents
Section titled “Incidents”A failed deployment (severity critical) or build (warning) opens an incident for its app. If it fails again, Kuben adds to the same open incident instead of opening a new one. The next success resolves it. Stale database backups open an organization-wide incident, which Kuben resolves once a fresh backup exists; it shows on the Incidents page but sends no webhook.
curl -H "Authorization: Bearer $TOKEN" https://kuben.example.com/api/v1/incidents- Acknowledge an incident (
POST /api/v1/incidents/{id}/acknowledge) to show that someone is working on it. - Resolve it by hand (
POST /api/v1/incidents/{id}/resolve). - Add
?all=trueto the list request to include resolved incidents.
Every member can see incidents; acknowledging and resolving need the developer role or higher. Resolved incidents are deleted after 180 days (retention.resolved_incident_days).
Silences
Section titled “Silences”A silence mutes incident webhooks for an environment or one app, for up to 7 days; the incidents are still recorded, and deployment.* and build.* events are still sent. On the environment page, under Alert silences, press Add a silence, give a Reason and an end, and choose what it Applies to: the whole environment or one app. Lift ends it early. Silences need the developer role or higher; the API is POST /api/v1/projects/{project}/environments/{env}/silences.
Webhooks
Section titled “Webhooks”Owners add endpoints on the Webhooks page, or with POST /api/v1/webhooks from a signed-in session (API tokens are refused), with a name, an https:// URL and a list of events (* means all events):
| Event | When |
|---|---|
deployment.started, deployment.succeeded, deployment.failed, deployment.cancelled |
A deployment is accepted or settles |
build.started, build.succeeded, build.failed, build.cancelled |
A Git build is queued or settles |
incident.opened, incident.resolved |
An incident changes |
ping |
You ask for one: POST /api/v1/webhooks/{id}/ping |
The response shows the endpoint’s signing secret (whsec_…). This is the only time Kuben shows it.
Delivery rules
Section titled “Delivery rules”- Retries. Kuben tries a delivery up to 12 times, waiting longer each time (from one minute up to six hours), and gives up after a day. Any
2xxanswer within 10 seconds counts as delivered. - Disabling. An endpoint is disabled after 20 deliveries in a row have used up their attempts.
- Stale events. Events more than an hour old when Kuben comes back from an outage are not sent.
- Deduplication. Each event has a unique
Kuben-Deliveryid; use it to ignore repeats.
To see recent deliveries, call GET /api/v1/webhooks/{id}/deliveries. To send a failed delivery again right away, call POST /api/v1/webhooks/{id}/deliveries/{delivery}/retry. Only failed deliveries of enabled endpoints can be retried; the retry gets the full number of attempts again, as long as the event is less than a day old. Deliveries are kept for 30 days (retention.webhook_delivery_days).
In the console
Section titled “In the console”Incidents in the sidebar lists open incidents (tick Show resolved to include the rest) with their severity, a Runbook link, when they opened and were last seen, and Acknowledge and Resolve buttons. Home shows the open ones too.
Webhooks in the sidebar adds endpoints (Add a webhook) and shows the signing secret once. For each endpoint, Deliveries lists what was sent with its HTTP status and error, Retry sends a failed one again, and Send a test sends a ping. Delete webhook disables an endpoint for good. Both pages are available in English and Persian.
Verifying the signature
Section titled “Verifying the signature”Every request carries a signature header:
Kuben-Signature: t=1758103200,v1=5f2c…v1 is the hex HMAC-SHA256, keyed with the secret, of <t>.<raw body>. To verify a request:
- Recompute the HMAC over the raw request body.
- Compare it to
v1in constant time. - Reject timestamps older than a few minutes.
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verify(secret: string, header: string, body: Buffer): boolean { const parts = Object.fromEntries(header.split(',').map((p) => p.split('='))) const age = Math.abs(Date.now() / 1000 - Number(parts.t)) if (!parts.v1 || age > 300) return false const mac = createHmac('sha256', secret).update(`${parts.t}.`).update(body).digest() const given = Buffer.from(parts.v1, 'hex') return given.length === mac.length && timingSafeEqual(given, mac)}GitHub commit statuses
Section titled “GitHub commit statuses”For apps that deploy from a GitHub repository, Kuben reports a commit status on the commit:
kuben/buildfor builds;kuben/<environment>for deployments.
The status is pending when the work starts and success, failure or error when it ends. It links to the app’s page when server.public_url is set. Kuben’s GitHub App needs the Commit statuses: write permission.