# Phase 6: Webhook Handler **Status:** ✅ Complete **Parent plan:** [PLAN.md](./PLAN.md) **Domain:** backend ## Objective Implement the secret UUID-based webhook endpoint that receives image push notifications from CI systems, with auto-creation of unknown projects. ## Tasks - [x] Task 1: Implement webhook HTTP handler — `POST /api/webhook/:secret-uuid` - [x] Task 2: Validate incoming payload — extract image name and tag - [x] Task 3: Look up project by image name in store — match against configured project images - [x] Task 4: If known project: match tag to stage via tag patterns, determine if auto_deploy - [x] Task 5: If unknown project: auto-create project with defaults from image inspection (EXPOSE port, labels) - [x] Task 6: Generate and store webhook secret UUID in settings (on first launch) - [x] Task 7: Implement webhook URL regeneration (new UUID, invalidates old one) - [x] Task 8: Define webhook payload struct (`{"image": "registry/org/app:tag"}`) ## Files to Modify/Create - `internal/webhook/handler.go` — webhook HTTP handler + payload parsing - `internal/webhook/matcher.go` — project/stage matching logic - `internal/webhook/autocreate.go` — auto-create project from unknown image ## Acceptance Criteria - Valid webhook URL with correct UUID triggers processing - Invalid/missing UUID returns 404 (no information leak) - Known images are matched to projects and stages - Unknown images trigger auto-creation with sensible defaults - Webhook URL can be regenerated ## Notes - Webhook URL format: `POST /api/webhook/d8f2a1e9-...` - No authentication needed beyond the secret UUID - Auto-created projects use: image EXPOSE port, "dev" as default stage, auto_deploy: true - The webhook handler calls into the deployer (Phase 7) — for now, define the interface/callback - Keep the handler thin — it matches and delegates ## Review Checklist - [x] All tasks completed - [x] No information leak on invalid UUIDs - [x] Payload validation rejects malformed input - [x] Auto-creation uses safe defaults - [x] Handler is stateless (delegates to store/deployer) ## Handoff to Next Phase ### Exported API - `webhook.NewHandler(store, deployer, inspector)` — creates the HTTP handler - `webhook.Handler.Route()` — returns a `chi.Router` to mount at `/api/webhook` - `webhook.EnsureWebhookSecret(store)` — generates UUID on first launch, returns current secret - `webhook.RegenerateWebhookSecret(store)` — replaces secret with new UUID, invalidates old one - `webhook.ParseImageRef(ref)` — parses `registry/owner/name:tag` into components ### Interfaces Defined - `webhook.DeployTriggerer` — `TriggerDeploy(ctx, projectID, stageID, imageTag) error` (mirrors `registry.DeployTriggerer`) - `webhook.ImageInspector` — `InspectImage(ctx, imageRef) (docker.ImageInfo, error)` (wraps `docker.Client`) ### Integration Points - Mount the webhook router: `r.Mount("/api/webhook", webhookHandler.Route())` - Call `webhook.EnsureWebhookSecret(store)` at application startup to generate the secret on first launch - The deployer must implement `webhook.DeployTriggerer` (same signature as `registry.DeployTriggerer`) - The Docker client (`*docker.Client`) satisfies `webhook.ImageInspector` directly ### Auto-Create Behavior - Unknown images create a project with name from image name, port from EXPOSE, healthcheck from image metadata - A default "dev" stage is created with `tag_pattern: "*"`, `auto_deploy: true`, `max_instances: 1` - If image inspection fails (not pulled locally), project is created with port=0 and empty healthcheck ### Tag Matching - Uses `path.Match` (glob semantics) — same approach as the registry poller - Stages are checked in name-sorted order; first matching stage wins