DocsGit & code

Webhooks

Event catalog, payload envelope, signature verification, delivery semantics, and SSRF hardening.

Configuring a webhook#

Repository- and organization-level webhooks (Settings → Webhooks) subscribe to a set of events and POST a JSON payload to a configured URL on each occurrence.

Event catalog#

PUSH, PULL_REQUEST_OPENED, PULL_REQUEST_UPDATED, PULL_REQUEST_MERGED, ISSUE_CREATED, ISSUE_CLOSED, RELEASE_CREATED, PIPELINE_COMPLETED.

Payload envelope#

For a generic endpoint, the request body is:

json
{
  "event": "PULL_REQUEST_OPENED",
  "payload": { "...": "event-specific fields" },
  "deliveredAt": "2026-01-01T00:00:00.000Z"
}

Headers and signature verification#

HeaderValue
X-KHUB-EventThe event name, matching payload.event.
X-KHUB-DeliveryA unique ID for this delivery attempt series — stable across retries of the same logical delivery, usable for idempotency on the receiving end.
X-KHUB-Signature-256sha256=<hex hmac> — an HMAC-SHA256 of the exact raw request body, keyed with the webhook's per-webhook secret (shown once at creation).

Verify it (Node.js) before trusting a payload:

verify-signature.js
const crypto = require("crypto");

function isValid(rawBody, signatureHeader, secret) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}

Always compute the HMAC over the raw request body bytes, before any JSON parsing — re-serializing a parsed body can reorder keys or change whitespace and invalidate the comparison.

Delivery semantics#

  • Up to 3 delivery attempts per event occurrence; a 3xx response is treated as a failure (webhook endpoints are not followed through redirects, to avoid signature-verification ambiguity about which host actually received the payload).
  • Every attempt — including failures, response code, and response body — is recorded and viewable per-webhook, with a manual "redeliver" action for any past delivery.
  • Outbound requests are validated against a private/link-local IP allowlist before being sent (an SSRF guard), so a webhook URL cannot be used to make KHUB's server issue a request to its own internal network or cloud metadata endpoint.

Chat-platform auto-formatting#

If a webhook's URL is recognized as a Slack or Discord incoming-webhook URL, the payload is automatically reformatted into that platform's native message shape instead of the generic envelope above — pointing a webhook at a Slack "Incoming Webhook" URL produces a readable chat message with no adapter service required.