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

> Learn to automate background tasks using Routa's cron-style scheduling and webhook handlers in TypeScript and Rust. Effortlessly integrate external events into your workflows.

- Repository: [Fengda Huang/routa](https://github.com/phodal/routa)
- Tags: how-to-guide
- Published: 2026-05-26

---

**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:

- **`ScheduleStore`** ([`src/core/store/schedule-store.ts`](https://github.com/phodal/routa/blob/main/src/core/store/schedule-store.ts)): Persistence interface for schedule definitions, handling CRUD operations, enable/disable toggles, and due-list queries.
- **`Schedule` model** ([`src/core/models/schedule.ts`](https://github.com/phodal/routa/blob/main/src/core/models/schedule.ts)): Defines the data shape including cron expressions, prompts, agent IDs, and workspace associations.
- **`SchedulerService`** ([`src/core/scheduling/scheduler-service.ts`](https://github.com/phodal/routa/blob/main/src/core/scheduling/scheduler-service.ts)): In-process cron loop using `node-cron` that runs every minute, pulling due schedules and dispatching them. Skips execution on Vercel production where Vercel Cron Jobs take over.
- **`runScheduleTick`** ([`src/core/scheduling/run-schedule-tick.ts`](https://github.com/phodal/routa/blob/main/src/core/scheduling/run-schedule-tick.ts)): Core routine executed each tick that fetches due schedules, builds `BackgroundTask` objects, and updates `nextRunAt` timestamps.
- **`GitHubWebhookHandler`** ([`src/core/webhooks/github-webhook-handler.ts`](https://github.com/phodal/routa/blob/main/src/core/webhooks/github-webhook-handler.ts)): HTTP endpoint validating GitHub signatures and transforming payloads into background tasks.

## Creating and Managing Scheduled Tasks

### The Schedule Data Model

Schedules in Routa follow a strict schema defined in [`src/core/models/schedule.ts`](https://github.com/phodal/routa/blob/main/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`](https://github.com/phodal/routa/blob/main/crates/routa-server/src/api/schedules.rs). The Rust backend validates the payload and delegates to the TypeScript `ScheduleStore`.

```typescript
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`](https://github.com/phodal/routa/blob/main/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`](https://github.com/phodal/routa/blob/main/src/client/components/github-webhook-panel.tsx) or directly via the API. The registration persists in `GitHubWebhookStore` ([`src/core/store/github-webhook-store.ts`](https://github.com/phodal/routa/blob/main/src/core/store/github-webhook-store.ts)), which maintains URLs and secret keys per workspace.

```typescript
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`](https://github.com/phodal/routa/blob/main/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`](https://github.com/phodal/routa/blob/main/src/core/models/background-task.ts)) initializes tasks with:

```typescript
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`](https://github.com/phodal/routa/blob/main/src/core/store/background-task-store.ts)) and process through the agent runner, inheriting retry policies and logging facilities. The scheduler logs execution metrics:

```typescript
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`](https://github.com/phodal/routa/blob/main/src/core/store/schedule-store.ts) and [`src/core/models/schedule.ts`](https://github.com/phodal/routa/blob/main/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`](https://github.com/phodal/routa/blob/main/crates/routa-server/src/api/schedules.rs) and [`crates/routa-server/src/api/webhooks.rs`](https://github.com/phodal/routa/blob/main/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`](https://github.com/phodal/routa/blob/main/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`](https://github.com/phodal/routa/blob/main/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.