Skip to content

fix(coolify-webhook): handle unsigned payloads and underscore event names#42

Closed
finedesignz wants to merge 1 commit into
mainfrom
fix/coolify-webhook-unsigned
Closed

fix(coolify-webhook): handle unsigned payloads and underscore event names#42
finedesignz wants to merge 1 commit into
mainfrom
fix/coolify-webhook-unsigned

Conversation

@finedesignz
Copy link
Copy Markdown
Owner

Summary

  • Coolify's built-in webhook sender (4.0.0-beta) sends no signature headers — plain POST, no X-Coolify-Signature or X-Coolify-Timestamp
  • Coolify uses underscore event names (deployment_failed, deployment_success) not dot-notation (deployment.failed)
  • Hub previously rejected every Coolify webhook with 401 missing_signature and 400 bad_payload

Changes

  • HMAC check is now optional: if signature headers are absent, skip HMAC but still require a valid user_id + configured coolify_webhook_secret as the gate
  • Zod enum expanded to accept both deployment_failed/deployment_success/deployment_in_progress (Coolify built-in) and dot-notation variants (custom senders / future Coolify)
  • isFailure check covers both event name formats for triage dispatch

Coolify config status

  • Team 0 webhook_notification_settings: already configured with the correct URL, webhook_enabled=true, deployment_success=true, deployment_failure=true
  • Webhook URL: https://app.remo-code.com/api/coolify/webhook/233c6d63-5f44-43f4-9eae-efc34a00735a

Test plan

  • Trigger a test deployment failure on any Coolify app
  • Confirm 202 { ok: true, run_id: ... } in Coolify webhook logs
  • Confirm row appears in scheduled_task_runs with status=pending

🤖 Generated with Claude Code

Coolify's built-in webhook sender (as of 4.0.0-beta) sends unsigned POSTs
with no X-Coolify-Signature/X-Coolify-Timestamp headers and uses underscore
event names (deployment_failed, deployment_success) rather than dot-notation.

- Make HMAC optional: skip signature check when headers absent, require
  valid user_id + configured secret as the guard instead
- Expand Zod enum to accept both underscore and dot-notation event names
- Fix isFailure check and triage dispatch to cover both event name formats

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@finedesignz
Copy link
Copy Markdown
Owner Author

Superseded by the in-flight URL-token rewrite PR which folds in the event-name fix. URL-token auth replaces the HMAC-optional approach. Closing to avoid merge conflict.

@finedesignz finedesignz deleted the fix/coolify-webhook-unsigned branch May 26, 2026 03:45
finedesignz added a commit that referenced this pull request May 26, 2026
…webhook ingest (#44)

Coolify's webhook UI only exposes a single URL field (no headers, no signing),
so the original HMAC-header design was unusable against real deployments.
Replace it with a per-user token embedded in the URL path, while keeping the
legacy HMAC route mounted for a 30-day grace period.

Part 1 — URL-token auth
  - New primary route: POST /api/coolify/webhook/:user_id/:token
    token IS users.coolify_webhook_secret, constant-time compared.
  - Legacy POST /api/coolify/webhook/:user_id kept; returns Deprecation +
    Sunset headers and logs a warning per hit.
  - GET/POST /api/account/coolify-webhook-secret now returns the full
    URL with the token embedded.

Part 2 — Test-connection UI + audit log
  - New table coolify_webhook_attempts (capped 100/user, app-side trim).
  - Every hit logged: success, auth_failed, ip_rejected, bad_payload,
    legacy_hmac.
  - GET /api/account/coolify-webhook-attempts?limit=N (JWT).
  - SettingsPage Coolify Webhook card renders the last 10 attempts inline
    with status pill + IP + reason + relative timestamp + refresh button.

Part 3 — IP allowlist (defense in depth)
  - Optional users.coolify_webhook_allowed_ips (CSV of IPv4/IPv6/CIDR).
    NULL/empty = allow all (back-compat).
  - GET/PUT /api/account/coolify-webhook-allowed-ips (JWT, validates CIDR).
  - Zero-dep CIDR helper at hub/src/lib/cidr.ts (IPv4 + IPv6 + CIDR).
  - Rejected requests get 403 + audit row with reason
    'source_ip_not_in_allowlist'.

Coolify event-name fix (discovered mid-flight from PR #42 investigation)
  - Coolify's SendWebhookJob emits underscore event names
    (deployment_success, deployment_failed), not the dotted form.
  - Zod schema now accepts both and normalizes internally via EVENT_ALIAS.

Tests
  - 34 new/updated tests in hub/test/{coolify-webhook,cidr}.test.ts cover
    URL-token success + wrong-token, no-secret enumeration safety,
    underscore event aliasing, IP allowlist match/mismatch/CIDR/x-forwarded-for,
    legacy HMAC success + Deprecation header + every failure path.
  - Full hub suite: 132 pass / 0 fail.
  - web/bun run build: green.

Docs
  - docs/coolify-webhook-migration.md: new "2026-05-25 update" section
    documents URL-token auth, Coolify event-name shape, IP allowlist,
    legacy grace period, audit log.
  - CLAUDE.md: file-map entries updated to reflect new ingress shape +
    new endpoints + cidr helper.

User action post-deploy: re-rotate via Settings → Supervisor → Coolify
Webhook (the previously-pasted URL will keep working via the legacy HMAC
route, but Coolify can't actually send HMAC headers, so re-rotating to get
the new URL format is required for Coolify-originated webhooks to succeed).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant