Console

Webhooks

Receive signed events from Brunel. Event catalog with example bodies, the X-Brunel-Signature header and how to verify it, delivery log, redelivery and retention.

Webhooks are available on Business and above. Set the endpoint in Platform → Notifications, then follow deliveries in Platform → Webhooks.

Delivery

Brunel sends a POST with a JSON body to your HTTPS endpoint:

HeaderValue
Content-Typeapplication/json
User-AgentBrunel-Webhooks
X-Brunel-Eventthe event, e.g. tracking.alert
X-Brunel-Deliverythe delivery id
X-Brunel-Signaturesha256=<HMAC-SHA256 of the raw body with your signing secret>

Answer with any 2xx within 5 seconds. Redirects are not followed. Endpoints must be public addresses: private and internal ranges are refused, checked at each delivery.

Body

Envelope
{
  "id": "evt_Q2m9x3…",
  "event": "tracking.alert",
  "createdAt": "2026-09-26T09:00:00.000Z",
  "title": "acme/shop: 2 infrastructure changes on main",
  "lines": ["New datastore: Redis", "Estimated cost: +€38 per month"],
  "repo": "acme/shop",
  "url": "https://brunel.cloud/c/…",
  "data": {}
}

id is stable: a redelivery sends the same body with the same id, so you can de-duplicate.

Verify the signature

Compute the HMAC-SHA256 of the raw body with your signing secret and compare it with the header in constant time, before parsing the JSON.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody: string, header: string | null, secret: string): boolean {
  const expected = Buffer.from(`sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`);
  const got = Buffer.from(header ?? "");
  return got.length === expected.length && timingSafeEqual(got, expected);
}
Python
import hmac, hashlib

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header or "")

Delivery log and redelivery

Each delivery is logged with its attempts: time, duration, response code or error. Brunel makes one attempt per event; Redeliver sends the same body again, signed with the current secret, up to 10 attempts per delivery. Send a test event checks your endpoint. Deliveries are kept 30 days.

Rotate the signing secret

Rotate secret issues a new secret; later deliveries and redeliveries use it. Update your receiver first, then rotate.

Events

tracking.alert

Tracking alert. A push to a tracked branch changed the infrastructure, its cost or its security.

Example body
{
  "id": "evt_Q2m9…",
  "event": "tracking.alert",
  "createdAt": "2026-09-26T09:00:00.000Z",
  "title": "acme/shop: 2 infrastructure changes on main",
  "lines": [
    "New datastore: Redis",
    "Estimated cost: +€38 per month"
  ],
  "repo": "acme/shop",
  "url": "https://brunel.cloud/c/abc123",
  "data": {
    "commit": "9f1c2e7",
    "changes": 2
  }
}

pr.review

Pull request review. Brunel commented a pull request with its infrastructure, cost and security impact.

Example body
{
  "id": "evt_Q2m9…",
  "event": "pr.review",
  "createdAt": "2026-09-26T09:00:00.000Z",
  "title": "acme/shop#42 reviewed",
  "lines": [
    "Cost impact: +€12 per month",
    "1 security finding"
  ],
  "repo": "acme/shop",
  "url": "https://github.com/acme/shop/pull/42",
  "data": {
    "pullRequest": 42
  }
}

deploy.pr

Deployment pull request. A pull request with Terraform and CI was opened in your repository.

Example body
{
  "id": "evt_Q2m9…",
  "event": "deploy.pr",
  "createdAt": "2026-09-26T09:00:00.000Z",
  "title": "Deployment pull request opened for acme/shop",
  "lines": [
    "Tier: growth",
    "Provider: aws (eu-west-3)"
  ],
  "repo": "acme/shop",
  "url": "https://github.com/acme/shop/pull/43",
  "data": {
    "tier": "growth"
  }
}

price.report

Monthly price report. Costs recomputed with this month's provider catalogs, and the cheapest provider.

Example body
{
  "id": "evt_Q2m9…",
  "event": "price.report",
  "createdAt": "2026-09-26T09:00:00.000Z",
  "title": "Price report for acme/shop",
  "lines": [
    "growth: €212 per month (−€9)",
    "Cheapest: scaleway"
  ],
  "repo": "acme/shop",
  "url": "https://brunel.cloud/c/abc123",
  "data": {
    "month": "2026-09"
  }
}

api_key.expiring

API key expiring. An API key with an expiry reminder expires in 7 days.

Example body
{
  "id": "evt_Q2m9…",
  "event": "api_key.expiring",
  "createdAt": "2026-09-26T09:00:00.000Z",
  "title": "API key “CI” expires in 7 days",
  "lines": [
    "Rotate or replace it in Console → API keys."
  ],
  "repo": null,
  "url": "https://brunel.cloud/console/keys",
  "data": {
    "keyId": "3f9a1c0b2d4e"
  }
}

test

Test event. Sent from the console to check your endpoint.

Example body
{
  "id": "evt_Q2m9…",
  "event": "test",
  "createdAt": "2026-09-26T09:00:00.000Z",
  "title": "Test event from the Brunel console",
  "lines": [],
  "repo": null,
  "url": null,
  "data": {}
}
Webhooks · Brunel Docs