Agent skill
Buildkite Webhooks Skill
Receive and verify Buildkite webhooks. Use when setting up Buildkite pipeline webhook handlers, debugging X-Buildkite-Token or X-Buildkite-Signature verification, or handling CI/CD events like build.finished, job.finished, build.failing, or agent.connected.
Install this skill
npx skills add hookdeck/webhook-skills --skill buildkite-webhooks
When to Use This Skill
- How do I receive Buildkite webhooks?
- How do I verify the
X-Buildkite-Tokenheader? - How do I verify an
X-Buildkite-SignatureHMAC signature? - How do I handle
build.finished,job.finished, oragent.connectedevents? - Why is my Buildkite webhook verification failing?
Essential Code (USE THIS)
Buildkite sends one of two headers on every webhook, depending on how the webhook's Token is configured in Notification Services:
- Token mode (default): the token is sent as plain text in
X-Buildkite-Token. Compare it to your stored token in constant time. There is no body signature, so anyone who sees the token can forge requests. - Signature mode (more secure, optional):
X-Buildkite-Signature: timestamp=<unix>,signature=<hex>. The signature is HMAC-SHA256 (hex) of"<timestamp>.<raw body>", keyed with the webhook token.
If the webhook goes through Hookdeck Event Gateway, use token mode: Hookdeck's Buildkite source verifies X-Buildkite-Token, not X-Buildkite-Signature.
The event name is in the X-Buildkite-Event header (and event in the JSON body).
Verification (Node.js)
const crypto = require('crypto');
function safeEqual(a, b) {
const x = Buffer.from(a), y = Buffer.from(b);
return x.length === y.length && crypto.timingSafeEqual(x, y);
}
// rawBody: Buffer of the exact request bytes (express.raw, not express.json)
function verifyBuildkite(rawBody, headers, token) {
if (!token) return false;
const sigHeader = headers['x-buildkite-signature'];
if (sigHeader) {
// "timestamp=1619071700,signature=<hex>"
const parts = Object.fromEntries(sigHeader.split(',').map((kv) => kv.trim().split(/=(.*)/s).slice(0, 2)));
if (!parts.timestamp || !parts.signature) return false;
// Optional replay window, off by default: retries and replays keep the original timestamp
const expected = crypto.createHmac('sha256', token)
.update(`${parts.timestamp}.`).update(rawBody).digest('hex');
return safeEqual(parts.signature, expected);
}
const tokenHeader = headers['x-buildkite-token'];
return !!tokenHeader && safeEqual(tokenHeader, token);
}
Verification (Python)
import hashlib, hmac
def verify_buildkite(raw_body: bytes, headers, token: str) -> bool:
if not token:
return False
sig_header = headers.get("x-buildkite-signature")
if sig_header:
parts = dict(kv.strip().split("=", 1) for kv in sig_header.split(",") if "=" in kv)
if "timestamp" not in parts or "signature" not in parts:
return False
signed = parts["timestamp"].encode() + b"." + raw_body
expected = hmac.new(token.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(parts["signature"], expected)
return hmac.compare_digest(headers.get("x-buildkite-token", ""), token)
A request that carries X-Buildkite-Signature is never allowed to fall back to the token check.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Common Event Types
Subscribe to events when creating the webhook; the name arrives in X-Buildkite-Event.
| Event | Description |
|---|---|
ping | Webhook notification settings changed (sent when you save the webhook) |
build.scheduled | A build was scheduled |
build.running | A build started running |
build.failing | A build is failing (a job failed, build still running) |
build.finished | A build finished (check build.state) |
job.started | A command step job started on an agent |
job.finished | A job finished |
agent.connected | An agent connected to the API |
Others: build.skipped, job.scheduled, job.activated, job.promised_exit_status, agent.lost, agent.disconnected, agent.stopping, agent.stopped, agent.blocked, cluster_. See references/overview.md.
For the full event reference, see Buildkite pipelines webhooks.
Important Headers
| Header | Description |
|---|---|
X-Buildkite-Event | Event type, e.g. build.finished |
X-Buildkite-Token | Webhook token in plain text (token mode, the default) |
X-Buildkite-Signature | timestamp=<unix>,signature=<hex> (signature mode); timestamp is dispatch time |
Environment Variables
BUILDKITE_WEBHOOK_TOKEN=your_webhook_token # "Token" field of the webhook in Notification Services
BUILDKITE_TIMESTAMP_TOLERANCE_SECONDS=0 # optional; a positive value opts in to the replay window
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 buildkite --path /webhooks/buildkite
Reference Materials
- references/overview.md - Buildkite webhook concepts and events
- references/setup.md - Notification Services configuration
- references/verification.md - Token and signature verification details