Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Webhooks

A repository on a platform server (pandod server --platform) can call URLs when something happens in it, so CI, chat and issue trackers react to Grovedock the way they react to GitHub.

pando webhook add lumen/fleet https://ci.example.com/hooks/pando --event push --event change_request
pando webhook ping ID        # sends a ping now and shows the answer
pando webhook log ID         # recent deliveries

Or in the console: repository Settings → Webhooks (repository administrators).

Events

EventSent whenaction
pusha branch is created or moved (a land, a push, a rebase)created, updated
change_requesta change request is opened, closed or landedopened, closed, landed
reviewsomeone reviewsapproved, requested changes, commented
commentsomeone comments on a change request or an issuecreated
checkCI reports a checkreported
issuean issue is opened, closed, reopened or assignedopened, closed, reopened, assigned
pingyou ping the webhookping

A webhook with no events listed receives every event.

Requests

POST with a JSON body and these headers:

HeaderValue
X-Pando-Eventthe event, e.g. push
X-Pando-Deliverya unique id for this delivery; retries keep it
X-Pando-Signature-256sha256= and the hex HMAC-SHA256 of the body, keyed with the webhook’s secret

Every body has event, action, delivery, repository (owner, name, full_name), sender and at_ms. A push adds ref, before, after and head (id, author, message); change request events add change_request (number, title, author, state, source, target, source_tip, base, draft), and a landing adds landed_as; check adds check (name, state, summary, details_url) and the change request; issue adds issue (number, title, author, state, labels, assignees).

Verifying the signature

The signature is computed exactly as GitHub computes X-Hub-Signature-256, so existing libraries work. Compare in constant time, over the raw body:

// Node
const crypto = require("node:crypto");
const ok = (secret, body, header) => {
  const want = "sha256=" + crypto.createHmac("sha256", secret).update(body).digest("hex");
  return header.length === want.length && crypto.timingSafeEqual(Buffer.from(header), Buffer.from(want));
};
# Python
import hashlib, hmac
def ok(secret: bytes, body: bytes, header: str) -> bool:
    want = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, header)
// Go
func ok(secret, body []byte, header string) bool {
	m := hmac.New(sha256.New, secret)
	m.Write(body)
	return hmac.Equal([]byte("sha256="+hex.EncodeToString(m.Sum(nil))), []byte(header))
}

Delivery

  • Answer with any 2xx within 10 seconds. Anything else is retried with growing gaps (20 s up to about 20 minutes), eight attempts in all; redirects are not followed.
  • Delivery is at least once. Deliveries are queued in the registry and claimed with a lease, so a restart loses none; but a server that stops after posting and before recording the answer posts that delivery again. Receivers dedupe by the X-Pando-Delivery id (also the payload’s delivery field).
  • Each webhook gets at most 2 deliveries in flight, and each delivery at most 30 seconds, so a slow or dead endpoint delays only its own deliveries.
  • Finished deliveries (delivered, or given up after eight attempts) are deleted after 30 days.
  • Webhooks never call link-local, unspecified, multicast or reserved addresses, or cloud metadata services (169.254.169.254, 100.100.100.200, fd00:ec2::254), including IPv4 addresses written as IPv6 (mapped, NAT64, 6to4). By default they don’t call loopback or private networks (RFC 1918, 100.64.0.0/10, fc00::/7) either; a self-hosted server whose CI lives there starts with --webhook-allow-private. The address is checked when the webhook is saved and again at every delivery, and the delivery connects only to the address it checked, so a DNS answer that changes in between can’t redirect it.
  • A failed delivery reports the HTTP status, or only “unreachable”: never why a connection failed, so webhooks can’t be used to probe the server’s network.
  • Each repository may have up to 20 webhooks; the last 200 deliveries of each are kept.