# 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-256` signature?
* 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)

```javascript
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 without `sha256=`.
* 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 only `X-Signature-256` (the SHA-1 header is absent on FedRAMP).
* Missing, empty or malformed header, or a mismatch: return `401`.
* Python: `hmac.compare_digest` on **bytes**, never `str`.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/spacelift-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/spacelift-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/spacelift-webhooks/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 `5xx` responses. Return `401` for a bad signature (not retried) and `5xx` only 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

```bash
SPACELIFT_WEBHOOK_SECRET=your-webhook-secret   # the secret you set on the Spacelift webhook

```

## Local Development

```bash
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](https://github.com/hookdeck/webhook-skills/blob/main/skills/spacelift-webhooks/references/overview.md) - Webhooks, notification policies, message types, payloads
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/spacelift-webhooks/references/setup.md) - Create the webhook, set the secret, route with a policy
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/spacelift-webhooks/references/verification.md) - Signature details, gotchas, debugging