Docs

Alerts, notifications, webhooks & log drains

Rules that watch a project and page you by email or webhook, deploy notifications for Slack / Discord / email — plus the signature every outgoing POST carries so your receiver can trust it.

Metrics

Alert rules live on the project's Alerts tab. Each rule has a metric, a threshold, a delivery channel (email or webhook) and a target. Rules are evaluated every minute; a rule that fires will not fire again inside its window (5 minutes by default).

MetricThresholdFires when
cpu1–100 %CPU used by the project’s pods, as a share of their CPU limit (or request), stays at or above the threshold for the whole window. Sampled every minute from the cluster.
memory1–100 %Working-set memory as a share of the memory limit (or request) stays at or above the threshold for the whole window.
restart_loopcountContainer restarts on the production pods reach the threshold.
deploy_failed—The newest deployment failed (once per failure).
http_5xx_ratecount / min5xx responses per minute, averaged over the window, reach the threshold. Measured at the edge for every request to the project’s domains.
latency_p95msThe p95 response time over the window (edge-measured, request-weighted) reaches this many milliseconds.
cron_failedruns in a rowAny cron job in the project has failed this many consecutive runs. The payload names the job, its last HTTP status and error.
A cron job that answers 401 or 403 on every tick is the most common silent failure. Buildfyio sends Authorization: Bearer $CRON_SECRET only when that variable exists on the project — if your handler checks it, add CRON_SECRET on the Env variables tab and redeploy. The Cron tab flags any job that has failed five runs in a row and shows this hint; a cron_failed rule with threshold 5 turns it into a page.

CPU, memory, 5xx rate and latency are the same numbers the project's Analytics tab charts (last 24 h / 7 d): edge metrics come from the access log of every request to the project's domains, resource metrics from the cluster's per-minute samples.

Deploy notifications

Separate from alert rules, a project can post its deployment lifecycle to a channel. On the project's Settings page, Deploy notifications takes any number of destinations — a Slack incoming webhook, a Discord channel webhook or an email address — each subscribed to deploy.started, deploy.ready and/or deploy.failed. Messages carry the project, environment, branch and commit, the URL on ready and the failure reason on failed, with a link back to the deployment.

  • Slack — create an Incoming Webhook in your Slack app and paste the https://hooks.slack.com/services/… URL. Buildfyio posts a mrkdwn message.
  • Discord — Channel settings → Integrations → Webhooks → copy the https://discord.com/api/webhooks/… URL. Buildfyio posts an embed coloured by outcome.
  • Email — a plain-text message per event to one address.
Webhook URLs must be public hosts; the platform refuses private or internal addresses both when you save and when it sends. Use Send test on a destination to see a labelled sample arrive. Deliveries are best-effort: a failing destination shows last send failed on its row and does not hold up the deployment.

Webhook payload

Webhook rules POST a JSON object to your URL with content-type: application/json and user-agent: Buildfyio-Alerts/1.0. The fields depend on the metric; every payload carries metric, and a dashboard test carries test: true.

{
  "metric": "cron_failed",
  "project": "my-shop",
  "cronJobId": "cro_…",
  "cronJob": "retention-purge",
  "path": "/api/cron/retention",
  "consecutiveFailures": 12,
  "lastStatus": 401,
  "lastError": "HTTP 401: Cron secret invalid",
  "hint": "The endpoint rejected the call. If it checks Authorization: Bearer $CRON_SECRET, set CRON_SECRET in the project env variables."
}

Verifying the signature

Every webhook rule and every log drain has its own signing secret (whsec_…). It is shown once when the rule or drain is created and can be rotated with Regenerate secret; Buildfyio never displays it again. Each delivery carries:

X-Buildfyio-Signature: t=1760000000,v1=5f1c…e9a0
  • t — Unix time (seconds) when the request was signed.
  • v1 — hex HMAC-SHA256, keyed with your secret, over the string <t>.<raw request body> — the timestamp, a literal dot, then the exact bytes of the body.

To verify:

  • Read the raw body before any JSON parsing — re-serialising changes the bytes.
  • Reject if t is more than a few minutes from your clock (replay protection).
  • Recompute the HMAC and compare it to v1 with a constant-time comparison.
import { createHmac, timingSafeEqual } from 'node:crypto';

// Express / Fastify / Next.js route handler — read the RAW body, not a parsed object.
export function verifyBuildfyio(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=')));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return false; // replay window
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const given = String(parts.v1 ?? '');
  return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

// app.post('/hooks/buildfyio', express.raw({ type: '*/*' }), (req, res) => {
//   if (!verifyBuildfyio(req.body.toString(), req.header('x-buildfyio-signature') ?? '', process.env.BUILDFYIO_WEBHOOK_SECRET!)) {
//     return res.status(401).end();
//   }
//   const event = JSON.parse(req.body.toString());
//   ...
// });
Rules and drains created before signing shipped have no secret and send no header; the dashboard marks them unsigned. Press Add signing secret on the row to start signing them.

Log drains

Log drains are configured on the project's Data tab (the Logs page links there). Runtime log lines are batched and POSTed to each drain in the shape its type expects — raw JSON array for http / webhook, Datadog, Axiom and Loki push formats for the others — and the same X-Buildfyio-Signature header is computed over whichever body is sent, with the drain's own secret. Any auth token you store on the drain is still sent as before; the signature is in addition to it.

Outgoing webhooks

Beyond alert rules, a project (or a whole workspace) can register webhook endpoints on the Hooks tab under Outgoing webhooks. Each endpoint picks the events it wants and gets its own whsec_… signing secret, shown once. Every delivery is a JSON POST with content-type: application/json, user-agent: Buildfyio-Webhooks/1.0, the same X-Buildfyio-Signature header described above (verify it exactly the same way), plus X-Buildfyio-Event (the type), X-Buildfyio-Delivery (a unique id — use it to de-duplicate) and X-Buildfyio-Attempt.

EventSent when
deployment.createdA deployment starts building (once per deployment).
deployment.readyThe deployment is live; data.deployment.url is its URL.
deployment.failedBuild or deploy failed; data.deployment.reason says why.
deployment.cancelledCancelled by a user, superseded, or skipped by an ignore rule.
domain.verifiedA custom domain’s DNS now points at Buildfyio.
domain.failedA verified domain stopped resolving to us, or a new one is still unverified after an hour.
alert.firedAny alert rule of the project fired; data.alert is the alert payload shown above.

The body is always the same envelope:

{
  "type": "deployment.ready",
  "createdAt": "2026-10-21T09:12:44.120Z",
  "org": { "id": "org_…" },
  "project": { "id": "prj_…", "name": "my-shop", "slug": "my-shop" },
  "data": {
    "deployment": {
      "id": "dpl_…",
      "status": "ready",
      "env": "production",
      "branch": "main",
      "sha": "36cf1b7…",
      "author": "jane",
      "message": "fix checkout",
      "url": "https://my-shop.buildfyio.com",
      "reason": null,
      "dashboardUrl": "https://buildfyio.com/dashboard/deployments/dpl_…"
    }
  }
}
  • Respond 2xx quickly (within 10 seconds) and do the work afterwards. Any other status, a timeout or a connection error counts as a failed attempt.
  • Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h — seven attempts over roughly 24 hours — then marked failed. The Hooks tab lists every delivery with its status, last response code and error, and a Redeliver button that sends the same payload again as a new delivery.
  • Deliveries can arrive out of order and, after a retry, more than once; key your handling on X-Buildfyio-Delivery or on data.deployment.id + type.
  • Test on an endpoint row sends a ping event through the same pipeline so you can check your signature code before anything happens on the project.
  • Endpoint URLs must be public http(s) hosts — private, loopback and link-local addresses are refused when the endpoint is saved and again on every attempt.