From 0f25d3c55187b09eef8772bd5401a598e328a36d Mon Sep 17 00:00:00 2001 From: senke Date: Tue, 10 Feb 2026 21:11:32 +0100 Subject: [PATCH] fix(webhooks): add DB migration and avoid 500 toast on developer portal Backend: - Add migrations/075_create_webhooks.sql: webhooks + webhook_failures tables - Fixes GET /webhooks 500 (relation "webhooks" did not exist) Frontend: - Skip toast for 5xx on /webhooks so developer portal shows empty state instead of 'Une erreur serveur s'est produite' when table is missing Co-authored-by: Cursor --- apps/web/src/services/api/client.ts | 5 ++- .../migrations/075_create_webhooks.sql | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 veza-backend-api/migrations/075_create_webhooks.sql diff --git a/apps/web/src/services/api/client.ts b/apps/web/src/services/api/client.ts index 50795d399..840cc8cf6 100644 --- a/apps/web/src/services/api/client.ts +++ b/apps/web/src/services/api/client.ts @@ -1832,12 +1832,15 @@ apiClient.interceptors.response.use( // FE-COMP-005: Show toast notification for API errors (unless disabled) // Skip toast for "wrong server" (HTML instead of JSON): already shown in response interceptor const isWrongServerError = apiError.message?.includes('HTML page instead of JSON') ?? false; + const urlForToast = originalRequest?.url ?? ''; + const isWebhooks5xxForToast = status && status >= 500 && urlForToast.includes('/webhooks'); const shouldShowToast = !(originalRequest as any)?._disableToast && status !== 401 && // Don't show toast for 401 (handled by refresh/redirect) status !== 404 && // Don't show toast for 404 (handled by router) !axios.isCancel(error) && // Don't show toast for cancelled requests - !isWrongServerError; + !isWrongServerError && + !isWebhooks5xxForToast; // 5xx on webhooks: table may be missing, show empty state instead // FIX: Implement toast throttling for network errors to prevent spam const isNetworkError = !error.response; diff --git a/veza-backend-api/migrations/075_create_webhooks.sql b/veza-backend-api/migrations/075_create_webhooks.sql new file mode 100644 index 000000000..79d183406 --- /dev/null +++ b/veza-backend-api/migrations/075_create_webhooks.sql @@ -0,0 +1,42 @@ +-- 075_create_webhooks.sql +-- Webhooks and webhook delivery failure tracking (BE-SEC-012) + +-- === WEBHOOKS === +CREATE TABLE IF NOT EXISTS public.webhooks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, + + url TEXT NOT NULL, + events TEXT[] NOT NULL DEFAULT '{}', + + active BOOLEAN NOT NULL DEFAULT true, + secret TEXT NOT NULL DEFAULT '', + api_key VARCHAR(64) NOT NULL, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_webhooks_api_key ON public.webhooks(api_key) WHERE api_key != ''; +CREATE INDEX IF NOT EXISTS idx_webhooks_user_id ON public.webhooks(user_id); +CREATE INDEX IF NOT EXISTS idx_webhooks_active ON public.webhooks(active) WHERE active = true; + +COMMENT ON TABLE public.webhooks IS 'Developer webhooks for receiving event notifications'; +COMMENT ON COLUMN public.webhooks.api_key IS 'API key for webhook authentication (BE-SEC-012)'; + +-- === WEBHOOK FAILURES === +CREATE TABLE IF NOT EXISTS public.webhook_failures ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + webhook_id UUID NOT NULL REFERENCES public.webhooks(id) ON DELETE CASCADE, + + event VARCHAR(255) NOT NULL, + error TEXT NOT NULL, + retries INT NOT NULL DEFAULT 0, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_webhook_failures_webhook_id ON public.webhook_failures(webhook_id); +CREATE INDEX IF NOT EXISTS idx_webhook_failures_created_at ON public.webhook_failures(created_at DESC); + +COMMENT ON TABLE public.webhook_failures IS 'Webhook delivery failure log for retry and debugging';