{"record":{"id":"8b1ade30af23af13","repo":"odysseus-dev/odysseus","slug":"webhook-not-found","errorCode":null,"errorMessage":"Webhook not found","messagePattern":"Webhook not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"routes/webhook/webhook_routes.py","lineNumber":148,"sourceCode":"                url=url,\n                secret=encrypted_secret,\n                events=events,\n                is_active=True,\n            ))\n            db.commit()\n        finally:\n            db.close()\n\n        return {\"id\": webhook_id, \"name\": name}\n\n    @router.post(\"/webhooks/{webhook_id}/test\")\n    async def test_webhook(request: Request, webhook_id: str):\n        _require_admin(request)\n        db = SessionLocal()\n        try:\n            wh = db.query(Webhook).filter(Webhook.id == webhook_id).first()\n            if not wh:\n                raise HTTPException(404, \"Webhook not found\")\n            url, secret = wh.url, wh.secret\n        finally:\n            db.close()\n\n        await webhook_manager.deliver_test(webhook_id, url, secret)\n        return {\"status\": \"sent\"}\n\n    @router.patch(\"/webhooks/{webhook_id}\")\n    def toggle_webhook(request: Request, webhook_id: str):\n        _require_admin(request)\n        db = SessionLocal()\n        try:\n            wh = db.query(Webhook).filter(Webhook.id == webhook_id).first()\n            if not wh:\n                raise HTTPException(404, \"Webhook not found\")\n            wh.is_active = not wh.is_active\n            db.commit()\n            return {\"id\": webhook_id, \"is_active\": wh.is_active}","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/webhook/webhook_routes.py#L130-L166","documentation":"HTTP 404 from POST /webhooks/{webhook_id}/test. The route looks the webhook up by its 8-character short id in the Webhook table; no row matches, so the test delivery is refused. Distinct from delivery failures — the webhook record itself does not exist.","triggerScenarios":"Calling the test endpoint with an id that was deleted, a typo'd id, an id from a different database/environment, or before the creating transaction of a brand-new webhook is visible to this SessionLocal.","commonSituations":"UI still showing a stale webhook list after a delete; copy-paste of the wrong 8-char id (they are short and collision-prone); testing against a fresh dev database that was re-seeded while the client held old ids.","solutions":["Re-fetch the webhook list (GET /webhooks) and use a current id","If the webhook was deleted, recreate it first, then test","Confirm you are pointed at the same server/database the webhook was created on"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"hooks = requests.get(f\"{base}/api/webhooks\", headers=h).json()\nids = {w[\"id\"] for w in hooks}\nassert webhook_id in ids, f\"{webhook_id} no longer exists; refresh list\"","typeGuard":null,"tryCatchPattern":"if resp.status_code == 404:\n    # webhook record gone: refresh list, recreate, or drop it from UI","preventionTips":["Always derive ids from a fresh GET /webhooks instead of caching them","Treat a delete anywhere in the app as invalidating other tabs' ids"],"tags":["webhook","not-found","fastapi","http-404"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}