-- ============================================ -- WEBHOOKS: Delivery Log Table -- ============================================ -- Delivery attempts table CREATE TABLE webhooks.deliveries ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), webhook_id UUID NOT NULL REFERENCES webhooks.webhooks(id) ON DELETE CASCADE, tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, -- Event info event_type TEXT NOT NULL, payload JSONB NOT NULL, -- Delivery status status webhooks.delivery_status NOT NULL DEFAULT 'pending', -- Response info response_status INTEGER, response_body TEXT, response_headers JSONB, -- Retry tracking attempt INTEGER NOT NULL DEFAULT 1, max_attempts INTEGER NOT NULL DEFAULT 5, next_retry_at TIMESTAMPTZ, last_error TEXT, -- Timestamps created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), delivered_at TIMESTAMPTZ, completed_at TIMESTAMPTZ ); -- Indexes CREATE INDEX idx_deliveries_webhook ON webhooks.deliveries(webhook_id); CREATE INDEX idx_deliveries_tenant ON webhooks.deliveries(tenant_id); CREATE INDEX idx_deliveries_status ON webhooks.deliveries(status) WHERE status IN ('pending', 'retrying'); CREATE INDEX idx_deliveries_retry ON webhooks.deliveries(next_retry_at) WHERE status = 'retrying' AND next_retry_at IS NOT NULL; CREATE INDEX idx_deliveries_created ON webhooks.deliveries(created_at DESC); -- RLS ALTER TABLE webhooks.deliveries ENABLE ROW LEVEL SECURITY; CREATE POLICY deliveries_tenant_isolation ON webhooks.deliveries FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); -- Function to calculate next retry time with exponential backoff CREATE OR REPLACE FUNCTION webhooks.calculate_next_retry(attempt INTEGER) RETURNS TIMESTAMPTZ AS $$ BEGIN -- Retry schedule: 1min, 5min, 30min, 2hours, 6hours RETURN CASE attempt WHEN 1 THEN NOW() + INTERVAL '1 minute' WHEN 2 THEN NOW() + INTERVAL '5 minutes' WHEN 3 THEN NOW() + INTERVAL '30 minutes' WHEN 4 THEN NOW() + INTERVAL '2 hours' WHEN 5 THEN NOW() + INTERVAL '6 hours' ELSE NULL -- No more retries END; END; $$ LANGUAGE plpgsql IMMUTABLE; -- Function to get pending deliveries for retry CREATE OR REPLACE FUNCTION webhooks.get_pending_retries(batch_size INTEGER DEFAULT 100) RETURNS SETOF webhooks.deliveries AS $$ BEGIN RETURN QUERY SELECT * FROM webhooks.deliveries WHERE status = 'retrying' AND next_retry_at <= NOW() ORDER BY next_retry_at ASC LIMIT batch_size FOR UPDATE SKIP LOCKED; END; $$ LANGUAGE plpgsql; -- Function to get delivery stats for a webhook CREATE OR REPLACE FUNCTION webhooks.get_webhook_stats(p_webhook_id UUID) RETURNS TABLE ( total_deliveries BIGINT, successful_deliveries BIGINT, failed_deliveries BIGINT, pending_deliveries BIGINT, success_rate NUMERIC, last_delivery_at TIMESTAMPTZ ) AS $$ BEGIN RETURN QUERY SELECT COUNT(*)::BIGINT as total_deliveries, COUNT(*) FILTER (WHERE status = 'delivered')::BIGINT as successful_deliveries, COUNT(*) FILTER (WHERE status = 'failed')::BIGINT as failed_deliveries, COUNT(*) FILTER (WHERE status IN ('pending', 'retrying'))::BIGINT as pending_deliveries, CASE WHEN COUNT(*) FILTER (WHERE status IN ('delivered', 'failed')) > 0 THEN ROUND( COUNT(*) FILTER (WHERE status = 'delivered')::NUMERIC / COUNT(*) FILTER (WHERE status IN ('delivered', 'failed'))::NUMERIC * 100, 2 ) ELSE 0 END as success_rate, MAX(delivered_at) as last_delivery_at FROM webhooks.deliveries WHERE webhook_id = p_webhook_id; END; $$ LANGUAGE plpgsql STABLE; -- Comments COMMENT ON TABLE webhooks.deliveries IS 'Webhook delivery attempts and status tracking'; COMMENT ON COLUMN webhooks.deliveries.attempt IS 'Current attempt number (1-based)'; COMMENT ON COLUMN webhooks.deliveries.next_retry_at IS 'When to retry if status is retrying'; COMMENT ON FUNCTION webhooks.calculate_next_retry IS 'Calculate next retry time with exponential backoff'; COMMENT ON FUNCTION webhooks.get_pending_retries IS 'Get deliveries ready for retry processing'; COMMENT ON FUNCTION webhooks.get_webhook_stats IS 'Get delivery statistics for a webhook';