AutoDisableFailingWebhooks scans for webhooks whose recent delivery history exceeds the failure threshold and flips them to enabled=false with auto_disabled_at = now(). Returns the count of webhooks newly disabled. Designed to be called periodically (e.g. every 5 minutes) from a janitor goroutine.
(ctx context.Context)
| 503 | // rows reached status='delivered'. The zero-delivered guard prevents |
| 504 | // a noisy webhook that's still mostly working from being disabled. |
| 505 | func (s *Store) AutoDisableFailingWebhooks(ctx context.Context) (int, error) { |
| 506 | rows, err := s.pool.Query(ctx, |
| 507 | `UPDATE webhooks |
| 508 | SET enabled = false, |
| 509 | auto_disabled_at = now() |
| 510 | WHERE id IN ( |
| 511 | SELECT webhook_id |
| 512 | FROM webhook_subscriber_deliveries |
| 513 | WHERE created_at > now() - $2::interval |
| 514 | GROUP BY webhook_id |
| 515 | HAVING COUNT(*) FILTER (WHERE status = 'failed') >= $1 |
| 516 | AND COUNT(*) FILTER (WHERE status = 'delivered') = 0 |
| 517 | ) |
| 518 | AND enabled = true |
| 519 | RETURNING id`, |
| 520 | AutoDisableThreshold, AutoDisableWindow, |
| 521 | ) |
| 522 | if err != nil { |
| 523 | return 0, fmt.Errorf("auto-disable scan: %w", err) |
| 524 | } |
| 525 | defer rows.Close() |
| 526 | count := 0 |
| 527 | for rows.Next() { |
| 528 | var id string |
| 529 | if err := rows.Scan(&id); err != nil { |
| 530 | return count, err |
| 531 | } |
| 532 | count++ |
| 533 | } |
| 534 | return count, rows.Err() |
| 535 | } |
| 536 | |
| 537 | // ClearExpiredPrevSecrets nulls signing_secret_prev / |
| 538 | // signing_secret_prev_expires_at on rows past their grace window. |