How to Set Up Schedules, Webhooks, and Background Task Automation in Routa

Routa provides a built-in cron-style scheduling system combined with webhook handlers that convert external events into background tasks, running through coordinated TypeScript services and Rust API endpoints.

Setting up automation in the Routa repository requires understanding how scheduled jobs, webhook triggers, and background task execution work together. The architecture uses a hybrid TypeScript/Rust stack where scheduling logic resides in the core TypeScript services while API endpoints are handled by the Rust server in crates/routa-server.

Understanding Routa's Automation Architecture

Routa unifies periodic and event-driven automation through a shared background task system. Whether triggered by a cron schedule or a GitHub push event, every automation flow ultimately creates a BackgroundTask that the agent runner processes.

The system relies on five coordinated components:

Creating and Managing Scheduled Tasks

The Schedule Data Model

Schedules in Routa follow a strict schema defined in src/core/models/schedule.ts. Each schedule requires a cron expression, task prompt, target agent, and workspace ID. The model uses cron-utils to compute next run times dynamically.

Key fields include:

  • cronExpr: Standard cron syntax (e.g., 0 9 * * * for 9 AM daily)
  • taskPrompt: The instruction passed to the agent
  • agentId: Which ACP agent executes the task (e.g., opencode)
  • enabled: Boolean toggle for activation
  • nextRunAt: Computed timestamp for the next execution

Configuring Cron Expressions

Routa supports standard cron syntax through the getNextRunTime helper in the Schedule model. The SchedulerService recalculates nextRunAt after each execution using the cron-utils library.

Common patterns include:

  • 0 * * * * – Every hour on the hour
  • 0 9 * * 1 – Every Monday at 9 AM
  • */15 * * * * – Every 15 minutes

API Endpoint for Schedule Creation

Create schedules by POSTing to the /api/schedules endpoint implemented in crates/routa-server/src/api/schedules.rs. The Rust backend validates the payload and delegates to the TypeScript ScheduleStore.

await fetch('/api/schedules', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Daily summary',
    cronExpr: '0 9 * * *',          // 09:00 UTC every day
    taskPrompt: 'Generate a daily summary of the workspace',
    agentId: 'opencode',
    workspaceId: 'my-workspace',
    enabled: true,
  }),
});

The web interface provides visual management through src/client/components/schedule-panel.tsx, which includes manual execution triggers via the runNow function.

Implementing Webhook Automation

GitHub Webhook Registration

Register external webhooks through the UI component in src/client/components/github-webhook-panel.tsx or directly via the API. The registration persists in GitHubWebhookStore (src/core/store/github-webhook-store.ts), which maintains URLs and secret keys per workspace.

await fetch('/api/webhooks/github', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    workspaceId: 'my-workspace',
    repoUrl: 'https://github.com/example/repo',
    secret: '••••••',                     // Stored safely; never exposed in logs
    events: ['push', 'pull_request'],
    taskPrompt: 'Run the code-review agent for this PR',
  }),
});

Payload Validation and Task Creation

When GitHub POSTs to /api/webhooks/github, the GitHubWebhookHandler validates the HMAC signature against the stored secret. Upon validation, it extracts event data and creates a BackgroundTask using the createBackgroundTask factory function.

The handler supports event filtering (push, pull_request, etc.) and prompt templating, allowing dynamic task generation based on webhook payloads.

How Background Tasks Execute

The Scheduler Tick Process

Every minute, SchedulerService invokes runScheduleTick (src/core/scheduling/run-schedule-tick.ts). This function:

  1. Calls ScheduleStore.listDue() to fetch schedules where nextRunAt ≤ now and enabled is true
  2. Iterates through due schedules and invokes createBackgroundTask for each
  3. Persists tasks via BackgroundTaskStore.save()
  4. Updates schedule metadata: lastRunAt, lastTaskId, and recomputes nextRunAt

The service includes observability hooks through runWithSpan for distributed tracing.

Task Lifecycle and Retry Logic

Background tasks share the same execution path as user-initiated tasks. The createBackgroundTask function (src/core/models/background-task.ts) initializes tasks with:

export function createBackgroundTask(params: {
  id: string;
  prompt: string;
  agentId: string;
  workspaceId: string;
  title: string;
  triggerSource: 'schedule' | 'webhook';
  triggeredBy: string;
  maxAttempts?: number;
}) {
  return {
    ...params,
    status: 'queued',
    attempts: 0,
    createdAt: new Date(),
  };
}

Tasks queue in BackgroundTaskStore (src/core/store/background-task-store.ts) and process through the agent runner, inheriting retry policies and logging facilities. The scheduler logs execution metrics:

console.log(`[Scheduler] Tick fired ${result.fired} schedule(s): ${result.scheduleIds.join(', ')}`);

Summary

  • ScheduleStore and the Schedule model provide the data foundation for cron-based automation in src/core/store/schedule-store.ts and src/core/models/schedule.ts.
  • SchedulerService runs an in-process cron loop via node-cron, executing runScheduleTick every minute to dispatch due schedules as background tasks.
  • GitHubWebhookHandler converts external GitHub events into standardized background tasks, treating webhooks and schedules uniformly.
  • All automation flows generate BackgroundTask objects processed by the shared agent runner, ensuring consistent retry logic and observability across both periodic and event-driven triggers.
  • The Rust API layer in crates/routa-server/src/api/schedules.rs and crates/routa-server/src/api/webhooks.rs exposes HTTP endpoints for schedule and webhook management.

Frequently Asked Questions

How does Routa handle schedule execution on serverless platforms like Vercel?

The SchedulerService detects Vercel production environments and skips the in-process node-cron loop. Instead, it relies on Vercel Cron Jobs to trigger the schedule tick endpoint externally. On traditional servers, the service maintains its own one-minute interval loop.

Can I trigger a schedule manually outside of its cron expression?

Yes. The SchedulePanel component (src/client/components/schedule-panel.tsx) exposes a runNow function that immediately executes a schedule's associated task, bypassing the cron timing. This creates a background task identical to those generated by the automated tick process.

What happens if a background task fails during execution?

Tasks inherit retry logic from the shared agent runner infrastructure. The createBackgroundTask function accepts an optional maxAttempts parameter (defaulting to system policy), and the store tracks attempt counts. Failed tasks remain in the queue for retry according to the workspace's background task configuration.

How does the webhook handler verify GitHub signatures?

The GitHubWebhookHandler (src/core/webhooks/github-webhook-handler.ts) retrieves the stored secret from GitHubWebhookStore and validates the incoming payload's HMAC signature. Invalid signatures are rejected before conversion to background tasks, ensuring only authentic GitHub events trigger automation workflows.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →