Scheduled Scans

AllyProof can automatically scan your sites on a recurring schedule. Scheduled scans run in the background and send notifications when complete, so you always have up-to-date accessibility data without manual intervention.

How Scheduling Works

Scheduled scans use PostgreSQL's pg_cron extension inside Supabase. A cron job fires at the configured interval, calls an internal scan trigger endpoint, which then queues scans for all eligible sites in the organization.

pg_cron (Supabase)
  ├── Fires at scheduled time
  ├── Calls POST /api/internal/trigger-scheduled-scans
  │     (authenticated with CRON_SECRET)
  ├── Endpoint queries sites where scan_frequency matches
  └── Queues a scan job for each matching site

Setting Scan Frequency

Each organization has a scan_frequency setting that determines how often automatic scans run. This is configured in Settings > Scanning:

FrequencyScheduleAvailable on
dailyEvery day at 03:00 UTCAgency and Enterprise plans
weeklyEvery Monday at 03:00 UTCAll plans (Starter, Agency, Enterprise)
manualNo automatic scansAll plans

The 03:00 UTC time slot is chosen to minimize interference with peak traffic hours across most time zones and to avoid scanning during business-hours deployments.

What Happens During a Scheduled Scan

  1. pg_cron fires — The cron job executes at the scheduled time and sends a POST request to the internal trigger endpoint.
  2. Site selection — The endpoint queries for all sites belonging to organizations with matching scan_frequency and active subscriptions. Sites that are unverified or paused are skipped.
  3. Scan queuing — A scan job is created for each eligible site. Jobs are queued with a staggered start (30-second intervals) to avoid overwhelming the scanner workers.
  4. Scanning — Each scan follows the standard scan pipeline: page discovery, multi-engine analysis (axe-core + HTML_CodeSniffer + APCA), deduplication, and AI fix suggestion generation.
  5. Notification — When each scan completes, an email notification is sent to organization members with notifications enabled (see Email Notifications).

pg_cron Configuration

The cron jobs are set up in Supabase using the pg_cron extension. Two jobs are configured:

-- Daily scan trigger (runs every day at 03:00 UTC)
SELECT cron.schedule(
  'trigger-daily-scans',
  '0 3 * * *',
  $$
  SELECT net.http_post(
    url := current_setting('app.settings.app_url') || '/api/internal/trigger-scheduled-scans',
    headers := jsonb_build_object(
      'Content-Type', 'application/json',
      'Authorization', 'Bearer ' || current_setting('app.settings.cron_secret')
    ),
    body := '{"frequency": "daily"}'::jsonb
  );
  $$
);

-- Weekly scan trigger (runs every Monday at 03:00 UTC)
SELECT cron.schedule(
  'trigger-weekly-scans',
  '0 3 * * 1',
  $$
  SELECT net.http_post(
    url := current_setting('app.settings.app_url') || '/api/internal/trigger-scheduled-scans',
    headers := jsonb_build_object(
      'Content-Type', 'application/json',
      'Authorization', 'Bearer ' || current_setting('app.settings.cron_secret')
    ),
    body := '{"frequency": "weekly"}'::jsonb
  );
  $$
);

CRON_SECRET Setup

The internal trigger endpoint is protected by a shared secret to prevent unauthorized scan triggering. Set up the CRON_SECRET environment variable:

  1. Generate a random secret: openssl rand -hex 32
  2. Add it to your .env.local file:
    CRON_SECRET=your-generated-secret-here
  3. Set it in Supabase vault or app settings so pg_cron can reference it:
    ALTER DATABASE postgres SET app.settings.cron_secret = 'your-generated-secret-here';

The trigger endpoint validates the secret from the Authorization header and returns 401 Unauthorized if it does not match.

Monitoring Scheduled Scans

You can monitor scheduled scan activity in several ways:

  • Scan history — The site detail page shows all scans with their trigger source (manual, scheduled, or CI/CD)
  • pg_cron logs — Check cron.job_run_details in Supabase SQL editor to see job execution history and any errors
  • Email notifications — Enable scan completion notifications to get results delivered to your inbox

Troubleshooting

ProblemCauseSolution
Scans not runningpg_cron extension not enabledEnable via Supabase Dashboard > Database > Extensions
401 errors in cron logsCRON_SECRET mismatchVerify the secret matches between app env and Supabase settings
Sites skippedUnverified or paused sitesVerify site ownership and check the site is not paused
Scans queued but not completingScanner worker capacityCheck Fly.io machine status and scale up if needed