v2.3.0
What's new
BridgePort 2.3.0 adds outbound webhook subscriptions with HMAC-signed, retried delivery and Idempotency-Key support for safe POST retries, ships a typed OpenAPI spec (/openapi.json) generated from the real request schemas, and surfaces service-type/language badges and deployment servers in the UI. One database migration applies automatically on container start — no manual steps.
One behavior change to know about if you use multi-channel Slack routing and per-environment channel overrides together — see below.
Behavior changes
Per-environment Slack overrides now demultiplex fan-outs
Skip this if you don't set per-environment Slack channel overrides.
Previously, a per-environment Slack channel override only acted as a fallback for unrouted notification types. A type routed to multiple channels globally fired into all of them from every environment, so you couldn't mute one environment's noise without losing another's.
Now an override collapses a fan-out to that environment's own channel:
- Exactly one matched route → sent there; the override is not consulted (env-agnostic single routes still reach every environment).
- More than one match (fan-out) → if the originating environment has a usable override, send only to that environment's channel; otherwise the full fan-out is preserved.
- No match → environment override as fallback, else the global default (unchanged).
Impact: any environment that has both a multi-channel routing and a per-environment override set will now receive only its override channel instead of the full fan-out. Single-routed types and unrouted-fallback behavior are unchanged. To split a shared alert per environment: route the type to >1 channel globally, then set each environment's override to its own channel. (#221)
Database migrations
One migration, applied automatically on container start (BridgePort's golden rule — zero human intervention).
20260611215341_webhooks_idempotency— adds three tables:WebhookSubscription(env-scoped endpoints, encrypted signing secret),WebhookDelivery(per-attempt delivery records with retry state), andIdempotencyKey(24h dedupe store for mutating POSTs). Additive only — no changes to existing tables. (#234)
Features
Webhook subscriptions (#234)
Environment-scoped outbound webhooks let external systems react to BridgePort events in near-real-time, as an alternative to the existing admin-scoped notification fan-out.
- Manage subscriptions under
/api/environments/:envId/webhooks(create / list / get / delete), with delivery history atGET …/:id/deliveries. - Deliveries are HMAC-signed (
X-BridgePort-Signature: sha256=…), sent in the background, and retried with exponential backoff (5 attempts). - Terminal events fire within seconds:
deployment.completed/failed,plan.completed/failed,backup.completed/failed,sync.completed. - Signing secrets are encrypted at rest (AES-256-GCM) and never returned by the API (
hasSecretboolean only). - This is a separate system from the admin-scoped
WebhookConfignotifications (X-Webhook-Signature) — different contracts, kept side by side rather than retrofitted.
Idempotency-Key for safe POST retries (#234)
Mutating POSTs now honor a client-supplied Idempotency-Key header. A global hook dedupes against the key for a 24-hour window and replays the original response, so a client can safely retry a POST whose outcome is unknown.
- Same key, mismatched body →
409 IDEMPOTENCY_KEY_REUSED. - Same key, request still in progress →
409.
Typed OpenAPI spec (#227)
/openapi.json and Swagger UI now carry real request contracts, generated from the existing Zod validation schemas — one source of truth, no doc/validation drift.
- A checked-in
openapi.jsonsnapshot plus a CI drift check that fails if routes change without regenerating the spec. - Documentation-only (uses Zod 4's native
z.toJSONSchema(), no new runtime deps); runtime validation and the custom error envelope are unchanged.
Service-type & language badges, deployment servers (#223)
- Service-type badge on each service card (promoted from inline text; keeps the
Genericfallback). - Config-file language badge (e.g.
yaml,nginx) on text config files, alongside the binary badge. - Servers on service detail — a linked list of deployment servers above Deployment History, plus a new linked Server column in the history table (
—for legacy rows). No schema change.
API changes
Added
POST /api/environments/:envId/webhooks— create a webhook subscription. (#234)GET /api/environments/:envId/webhooks— list subscriptions. (#234)GET /api/environments/:envId/webhooks/:id— get a subscription. (#234)DELETE /api/environments/:envId/webhooks/:id— delete a subscription. (#234)GET /api/environments/:envId/webhooks/:id/deliveries— delivery history. (#234)Idempotency-Keyrequest header honored on mutating POSTs. (#234)serviceDeployment.serverfield onGET /api/services/:id/deployments-historyresponses (nullfor legacy rows). (#223)- Typed request/error contracts now present throughout
/openapi.json. (#227)
Deprecated
- The sync result envelope's legacy
successalias is flaggeddeprecated: truein the OpenAPI spec — prefer the canonical field. (#227)
Removed
- None.
Security
- GHSA-w5hq-g745-h8pq (
uuid) — missing buffer bounds check in v3/v5/v6. Pinned to^11.1.1via pnpm override. Transitive and dev/tooling-only (hyperid←autocannon, stress tests); does not reach the production runtime or Docker image, and the vulnerable code path was never called. (#231) - GHSA-92pp-h63x-v22m (
@hono/node-server) —serveStaticmiddleware bypass via repeated slashes. Pinned to^1.19.13. Transitive and dev-only (@prisma/dev←prisma); not in the production runtime. (#231)
Under the hood
Toolchain migrated from npm to pnpm (#226)
The entire JS/TS toolchain now runs on a single pnpm 11 workspace (root backend + ui/), resolved by one root pnpm-lock.yaml. Supply-chain controls move to pnpm's native allowBuilds allowlist and minimumReleaseAge cooldown; CI, the multi-stage Docker build, and Dependabot were all converted. Dev keeps full strict phantom-dependency protection. Contributors: use pnpm install (see updated docs/development/).
UI dependency upgrades
- React 19 + recharts 3 —
react/react-dom18.3 → 19.2.7 andrecharts2.15 → 3.8.1 (client-side only; backend/agent/CLI untouched). (#220) - Tailwind CSS 4 — 3.4 → 4.3 (Oxide engine, CSS-first
@themeconfig, Vite plugin replaces the PostCSS pipeline). Like-for-like, no design changes. (#233)
Other notable bumps
golang.org/x/term(CLI) — 0.43.0 → 0.44.0 (#228)@types/testing-library__jest-dom(dev) — 5.14.9 → 6.0.0 (#229)/buildskill fix — runs the two scoped vitest configs instead of barenpm test(which produced ~759 false failures in this repo). (#224)
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgEyiv4hf6iBgr34ICjN6HnEP/vs
Yr31eNU5HhdkQaYd4AAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
AAAAQDDO04K4v3ckb7ElqF+Sfamr5V97DJ1h3Y37atrVlIRmgcKAmANljMESORjqwD+/f9
fINOxMjM6nyBRviMjfeg0=
-----END SSH SIGNATURE-----