Agent skill
Spacelift Webhooks Skill
Receive and verify Spacelift webhooks. Use when setting up a Spacelift named webhook endpoint, debugging X-Signature-256 signature verification, or handling run state change and internal error messages routed by notification policies.
Install this skill
npx skills add hookdeck/webhook-skills --skill spacelift-webhooks
Spacelift is an infrastructure-as-code orchestration platform. Named webhooks (Integrations -> Webhooks) receive the messages that your notification policies route to them: run state changes and internal errors. Per-stack webhooks still exist for backwards compatibility. Webhooks are available on paid accounts only.
Each delivery is signed with the webhook's secret using "the exact same mechanism as GitHub": an HMAC of the raw request body, sent in the X-Signature-256 header as sha256=<hex>.
Setup note
Set a secret on the Spacelift webhook. Without one, deliveries are unsigned and fail verification.
When to Use This Skill
- How do I receive Spacelift webhooks?
- How do I verify the Spacelift
X-Signature-256signature? - How do I tell Spacelift run events from internal error events?
- How do I order run updates with
stateVersion? - Why does my Spacelift webhook fail signature verification, or get auto-disabled?
Verification (core)
const crypto = require('crypto');
// rawBody: the request body as a Buffer, exactly as received (empty Buffer if none)
function verifySpaceliftSignature(rawBody, header, secret) {
if (!secret || typeof header !== 'string') return false;
let sig = header.trim().replace(/^sha256=/i, ''); // accept with or without the prefix
if (!/^[0-9a-fA-F]{64}$/.test(sig)) return false; // validate before decoding
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
const received = Buffer.from(sig.toLowerCase(), 'hex');
return crypto.timingSafeEqual(expected, received); // both 32 bytes
}
- Header:
X-Signature-256. Value:sha256=<64 hex chars>(lowercase hex HMAC-SHA256). The prefix is GitHub's format by reference; Spacelift's page doesn't print the value format, so accept it with or withoutsha256=. - Signed content is the raw body bytes only: no timestamp, id or other header is signed, so there is no replay window to check. Never parse and re-serialize before verifying.
- The HMAC key is the secret's UTF-8 bytes, used as-is (not hex- or base64-decoded). The secret is chosen when you create the webhook and cannot be edited: rotate by deleting and recreating the webhook.
- Spacelift also sends
X-Signature(HMAC-SHA1) on most installs; verify onlyX-Signature-256(the SHA-1 header is absent on FedRAMP). - Missing, empty or malformed header, or a mismatch: return
401. - Python:
hmac.compare_digeston bytes, neverstr.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
HTTP Methods
Spacelift's docs don't name the HTTP method. Hookdeck's Spacelift source pre-selects POST without managing the method, so any other method the sender uses must also be allowed on the source. The examples accept GET, POST, PUT, PATCH and DELETE, verify the signature first for every method, and treat a missing body as empty bytes.
Message Types
There is no event-name field or header. Tell the two documented payload shapes apart:
| Message | How to recognise it | Key fields |
|---|---|---|
run | has state, stateVersion, run and stack | account, state (e.g. FINISHED), stateVersion, timestamp, run.id, stack.id |
internal_error | has title and body, no state | title, body, error, severity, account |
Delivery can be out of order: use stateVersion to order updates for the same run and ignore a lower version than one already stored.
Delivery Behaviour
- Optional Retry on failure: up to 3 retries, only on
5xxresponses. Return401for a bad signature (not retried) and5xxonly for transient failures. - A webhook is auto-disabled after 10 or more consecutive failures and must be re-enabled by hand. Deliveries and their status codes are listed per webhook.
Environment Variables
SPACELIFT_WEBHOOK_SECRET=your-webhook-secret # the secret you set on the Spacelift webhook
Local Development
npx hookdeck-cli listen 3000 spacelift --path /webhooks/spacelift
No account required. Use the printed URL as the webhook endpoint in Spacelift.
Reference Materials
- references/overview.md - Webhooks, notification policies, message types, payloads
- references/setup.md - Create the webhook, set the secret, route with a policy
- references/verification.md - Signature details, gotchas, debugging