- Forge webhook POST /api/webhooks/forge HMAC sha256 verify, marker echodo-convoy → pr_review→merged as automation - Completion artifacts table 0017, deterministic summary template, gallery /artifacts - Ops copilot tRPC copilot + context tools (fleet, convoy, backlog) + panel + Draft Task → backlog.createTask - Doc-writer SOPs: webhook rotation, copilot prompt, published flow examples - Allow pr_review→merged as automation for webhook Convoy: .convoys/owner-cockpit-v02 Co-authored-by: orchestrator
3.3 KiB
3.3 KiB
Webhook secret rotation — FORGE_WEBHOOK_SECRET
One-page rotation SOP for POST /api/webhooks/forge HMAC secret.
Where it lives
- Coolify (prod):
webservice envFORGE_WEBHOOK_SECRETindocker/docker-compose.coolify.yml:web.environment→ Coolify UI → Environment Variables. Not committed; value lives only in Coolify env store and Forgejo webhook config. - Local dev:
.envat repo root (FORGE_WEBHOOK_SECRET=...). Copy from.env.examplepattern — never commit real value. - Consumer:
apps/web/app/api/webhooks/forge/route.ts:62readsprocess.env.FORGE_WEBHOOK_SECRETand verifiesX-Hub-Signature-256(GitHub) orX-Forgejo-Signature/X-Hub-SignatureHMAC-SHA256 withtimingSafeEqual. Rate-limit 60/min per IP. Missing secret → 500webhook_not_configured.
Rotate
-
Generate new secret (64 hex chars = 32 bytes):
openssl rand -hex 32Copy output → new
FORGE_WEBHOOK_SECRET. -
Update Forgejo / GitHub webhook:
- Forgejo: Repo → Settings → Webhooks → target webhook → Secret → paste new value → Save.
- GitHub: Repo → Settings → Webhooks → target webhook → Secret → paste new value → Save / Update webhook.
-
Update Coolify:
- Coolify →
echodoserviceweb→ Environment Variables → setFORGE_WEBHOOK_SECRET=<new-hex>→ Save. - Redeploy
web(Coolify Deploy). Verifydocker/docker-compose.coolify.ymlnot edited directly for prod; Coolify env overlay wins.
- Coolify →
-
For local dev, update
.env:FORGE_WEBHOOK_SECRET=<new-hex> pnpm --filter @tasks/web dev -
Keep old secret for 5 min overlap if Forgejo allows dual-secret testing; otherwise coordinate deploy + webhook save within same minute.
Test
Generate signature and POST to local or prod URL. Route accepts sha256=<hex> or bare <hex>.
SECRET="<new-hex>"
BODY='{"action":"closed","pull_request":{"merged":true,"body":"echodo-convoy: 00000000-0000-0000-0000-000000000000","number":123}}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
# Local
curl -i -X POST http://localhost:3000/api/webhooks/forge \
-H 'Content-Type: application/json' \
-H "X-Hub-Signature-256: sha256=$SIG" \
-d "$BODY"
# Forgejo variant (same HMAC, different header)
curl -i -X POST http://localhost:3000/api/webhooks/forge \
-H 'Content-Type: application/json' \
-H "X-Forgejo-Signature: sha256=$SIG" \
-d "$BODY"
# Prod
curl -i -X POST https://echodo.stillwell.cloud/api/webhooks/forge \
-H 'Content-Type: application/json' \
-H "X-Hub-Signature-256: sha256=$SIG" \
-d "$BODY"
Verify
- Valid HMAC +
action=closed+pull_request.merged=true+bodycontainsechodo-convoy: <uuid>matching a convoy inpr_review→ 200{"ok":true}and convoy transitionspr_review → merged(automation,pr#N merged) visible inconvoy_eventsand Mission Control within 5s. - Invalid signature → 401
{"ok":false,"error":"invalid_signature"}. - Missing
echodo-convoy:marker ormerged=falseorstatus !== pr_review→ 200{"ok":true}no-op (no leak). - Missing
FORGE_WEBHOOK_SECRETenv → 500webhook_not_configured.
References
- Route:
apps/web/app/api/webhooks/forge/route.ts - State machine:
packages/shared/src/convoy-lifecycle/state-machine.ts:90 - Checklist:
docs/operations/live-e2e-checklist.md