Skip to content

Consolidate /internal/*-{sync-notify,webhook} endpoints behind a single discriminated /internal/webhook #979

@jakebromberg

Description

@jakebromberg

Problem

apps/backend/routes/internal.route.ts carries seven internal webhook + sync-notify endpoints, each with its own route, its own payload shape, and its own auth check against X-Internal-Key:

  • POST /internal/flowsheet-sync-notify
  • POST /internal/rotation-sync-notify
  • POST /internal/artist-identity-sync-notify
  • POST /internal/flowsheet-webhook
  • POST /internal/rotation-webhook
  • POST /internal/streaming-status-webhook
  • POST /internal/parse (less webhook-shaped but still internal)

Each one duplicates the same shape: validate X-Internal-Key, log the event, dispatch to a domain handler. Adding a new ETL or sync source means adding another route + another auth check site + another test fixture for the auth bypass — and the permissions auditor has to re-verify each one separately, since they're not centrally enforced.

This isn't blocking anything today, but every new internal source compounds the surface area. The /internal/ namespace was already cited in the BS#937 / BS#965 fallout as one of the harder areas to audit when chasing what's allowed to mutate prod state.

End state

Single discriminated webhook endpoint:

POST /internal/webhook
X-Internal-Key: <secret>
Content-Type: application/json

{ "source": "flowsheet" | "rotation" | "artist-identity" | "streaming-status" | ..., "event": "sync-notify" | "webhook" | ..., "payload": {...} }
  • One auth check site.
  • One typed router (discriminator on source + event) dispatches to the domain handler.
  • Adding a new internal source = adding a discriminator variant + a handler, not a new route.
  • Existing endpoints stay live during the transition as deprecated aliases; remove after one deploy cycle (or whatever the safe-to-remove window is) once all callers migrate.

Acceptance

  • POST /internal/webhook exists, discriminated-payload validated via @wxyc/shared types (extend the contract there).
  • All seven existing internal callers (tubafrenzy webhook, ETL post-pass triggers, streaming-status callback) migrated to the new shape.
  • Existing endpoints retained as thin shims that forward to the new dispatcher; emit a Sentry warn the first time each is called so we can confirm migration completion before removal.
  • Permissions audit doc (docs/internal-endpoints.md or similar) lists the canonical surface as a single endpoint.
  • Existing integration tests pass against both the old and new shape during transition.

Constraints

  • Auth header / secret rotation: any change must roll cleanly across the BS deploy + the upstream caller deploys (some are tubafrenzy, soon to be retired; some are in BS itself).
  • Don't change the auth mechanism (still X-Internal-Key) — this is consolidation, not a security re-architecture.
  • Use the wxyc-shared codegen path for the discriminated payload type so cross-repo callers get typed clients.

Why now

Surfaced during the endpoint-discipline review on 2026-05-21. The tubafrenzy turndown at end of summer is going to remove three of these callers (the -webhook family) anyway — good time to consolidate the remaining ones before any new internal sources show up.

Related

  • Tubafrenzy turndown (end of summer)
  • BS#937 wedge RCA — flagged /internal/ namespace audit difficulty
  • The parent tubafrenzy parity work (some webhook traffic disappears with that)

Metadata

Metadata

Assignees

No one assigned

    Labels

    choreMaintenance and housekeepingenhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions