Vetrix Docs

Environment approval webhooks

Vetrix emits three webhook events around the environment-protection approval flow. External systems filter on the X-Vetrix-Event request header value to route each delivery.

Event identifier When it fires
environment.approved On every approval AND on the threshold-crossing vote.
environment.rejected On the first rejection. Kills the deployment.
deployment.review_requested When a deployment enters queued and addresses a reviewer who has not yet voted. Triggers the per-reviewer email.

Every delivery carries the standard headers:

  • X-Vetrix-Event — one of the identifiers above.
  • X-Vetrix-Signature-256 — HMAC-SHA256 over the raw JSON body, keyed with the webhook's signing secret.
  • Content-Type: application/json.

Payload shapes

environment.approved

{
  "deployment_id": "9d7f7b8e-…",
  "environment": "production",
  "approver_id": "5a1a6b5e-…",
  "comment": "looks good, shipping",
  "approvals": [
    { "reviewer_id": "5a1a6b5e-…", "action": "approved", "decided_at": "2026-04-19T18:02:11Z" },
    { "reviewer_id": "21d1ae2f-…", "action": "approved", "decided_at": "2026-04-19T18:03:44Z" }
  ],
  "required_count": 2,
  "approved_count": 2,
  "threshold_met": true,
  "delivered_at": "2026-04-19T18:03:44Z"
}

Receivers filter on threshold_met to decide whether the approval unblocked the run (the gate promotes the deployment to in_progress).

environment.rejected

{
  "deployment_id": "9d7f7b8e-…",
  "environment": "production",
  "rejector_id": "5a1a6b5e-…",
  "comment": "needs changelog entry",
  "delivered_at": "2026-04-19T18:03:44Z"
}

A single rejection kills the deployment regardless of how many other reviewers already approved — the notifier only ever delivers one environment.rejected per deployment.

deployment.review_requested

{
  "deployment_id": "9d7f7b8e-…",
  "environment": "production",
  "reviewer_id": "21d1ae2f-…",
  "delivered_at": "2026-04-19T18:01:03Z"
}

Delivered once per reviewer per deployment; used to trigger the per-reviewer email via the notification pipeline. The dedupe guarantee (no duplicate emails on re-request) lives at the notifier layer; the payload above is pure and idempotent at the webhook layer.

Eligibility helper (engine-side)

internal/environments/approval_webhook.go ships two pure helpers callers share across the HTTP handler + frontend eligibility check:

  • IsApproverEligible(actor, reviewers, prior) — returns true iff actor is in the reviewer allowlist and has not yet submitted an approval/rejection.
  • IsThresholdCrossing(prior, required) — returns true exactly once, on the vote that transitions the tally from below-required to at /above-required. Callers use this to decide whether to publish the environment.approved crossing event.

Deferred follow-ups

  • Frontend ApprovalInline.tsx component wired on the run-detail and environment-detail pages.
  • WebSocket broadcast at /ws/deployments/:id so a second reviewer's approval updates the UI within 1 s.
  • Playwright e2e harness.
  • MailCatcher fixture for the email dedupe guarantee.
  • Signed-payload receiver fixture.

The webhook event identifiers, payload shapes, and eligibility and threshold helpers shipping today are the stable contract the deferred frontend and e2e layers will consume.