{"record":{"id":"9193013efa9a1288","repo":"amir20/dozzle","slug":"failed-to-save-destination","errorCode":null,"errorMessage":"Failed to save destination","messagePattern":"Failed to save destination","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"assets/components/Notification/WebhookDestinationForm.vue","lineNumber":360,"sourceCode":"      type: \"webhook\",\n      url: webhookUrl.value.trim(),\n      template: template.value.trim() || undefined,\n      headers: headersToRecord(),\n    };\n\n    const url = props.isEditing\n      ? withBase(`/api/notifications/dispatchers/${props.destination!.id}`)\n      : withBase(\"/api/notifications/dispatchers\");\n\n    const res = await fetch(url, {\n      method: props.isEditing ? \"PUT\" : \"POST\",\n      headers: { \"Content-Type\": \"application/json\" },\n      body: JSON.stringify(input),\n    });\n\n    if (!res.ok) {\n      const data = await res.json();\n      throw new Error(data.error || \"Failed to save destination\");\n    }\n\n    registerGuard(null);\n    // Hand the saved destination back so callers can select it right away.\n    props.onCreated?.(await res.json().catch(() => undefined));\n    props.close?.();\n  } catch (e) {\n    error.value = e instanceof Error ? e.message : \"Failed to save destination\";\n  } finally {\n    isSaving.value = false;\n  }\n}\n\n// Unsaved-changes guard, matching the alert form: a stray Esc should not throw away a template.\nconst registerGuard = useDrawerCloseGuard();\nconst confirmingDiscard = ref(false);\nconst initial = ref<string>();\nconst snapshot = computed(() =>","sourceCodeStart":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/amir20/dozzle/blob/d9463cbe21874e44ab79db6fa63e746ca7d22928/assets/components/Notification/WebhookDestinationForm.vue#L342-L378","documentation":"WebhookDestinationForm.vue throws this when the POST that saves a webhook notification destination returns !res.ok. It prefers the server's JSON `error` field and falls back to the literal string 'Failed to save destination'.","triggerScenarios":"POST /api/notifications/dispatchers with webhook payload returns 4xx/5xx: invalid URL, unreachable test request on the server side, malformed body, or persistence failure writing the dispatchers file.","commonSituations":"Entering a webhook URL that the server cannot validate/reach (private IP, typo, https cert issues); notifications.yml is read-only; request rejected by reverse proxy with 401/404 HTML that isn't JSON.","solutions":["Inspect the response status and body in devtools; prefer the server `error` message over the fallback string","Verify the webhook URL is correct, reachable from the Dozzle host, and uses http/https","Check server logs for the dispatcher creation failure and that the data directory is writable","Ensure auth token/cookies are valid so the POST isn't rejected by middleware"],"exampleFix":"// before\nif (!res.ok) {\n  const data = await res.json();\n  throw new Error(data.error || \"Failed to save destination\");\n}\n// after\nif (!res.ok) {\n  const data = await res.json().catch(() => ({}));\n  throw new Error(data.error || `Failed to save destination (HTTP ${res.status})`);\n}","handlingStrategy":"validation","validationCode":"const url = new URL(input.url);\nif (!/^https?:$/.test(url.protocol)) throw new Error(\"Webhook URL must be http/https\");\nif (!input.name?.trim()) throw new Error(\"Name is required\");","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(endpoint, { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify(input) });\n  if (!res.ok) {\n    const data = await res.json().catch(() => ({}));\n    throw new Error(data.error || `Failed to save destination (HTTP ${res.status})`);\n  }\n} catch (e) {\n  formError.value = e instanceof Error ? e.message : String(e);\n}","preventionTips":["Validate the webhook URL client-side before POSTing","Parse the error body defensively (it may be HTML from a proxy)","Test the webhook endpoint reachability from the host before saving"],"tags":["http","vue","webhook","notifications"],"backgroundTag":"http-error-response","analyzedSha":"d9463cbe21874e44ab79db6fa63e746ca7d22928","analyzedAt":"2026-09-07T10:08:55.855Z","contentChangedAt":"2026-09-07T10:08:55.855Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}