# Cron webhooks: call any URL on a schedule

cronservices calls any HTTP endpoint on a cron schedule: a webhook, an internal route like `/api/cron/cleanup`, or a third-party API. Use it in place of a crontab line on a server you have to keep alive, a scheduled serverless function, or a "cron job" add-on that can't send headers.

- Any method, headers, and a JSON or text body, with `{{ }}` placeholders for dates and data.
- Schedules in any time zone, from every minute to "the last weekday of the month", or once at an exact time.
- Secrets in encrypted [credentials](/docs#credentials), sent as `Authorization: Bearer`, Basic auth, or an API key header.
- Every run recorded with its status code, duration, and the start of the response when it fails.

## Example: a nightly cleanup route

Call your cleanup endpoint at 02:00 New York time, with a shared secret the endpoint checks:

```json
{
  "name": "Nightly cleanup",
  "cron": { "expression": "0 2 * * *", "time_zone": "America/New_York" },
  "nodes": [
    { "id": "cleanup", "request": {
        "method": "POST",
        "endpoint": "https://app.example.com/api/cron/cleanup",
        "credential_id": "YOUR_SHARED_SECRET_ID",
        "body": "{ \"older_than\": \"{{ now() - days(30) }}\" }" } }
  ]
}
```

The body renders as `{ "older_than": "2026-09-30T06:00:00Z" }` and is sent as `application/json`.

## Example: chain two APIs

Each step receives the previous step's response, so you can read from one API and write to another in one job:

```json
{
  "name": "Sync open orders",
  "cron": { "expression": "*/10 * * * *" },
  "nodes": [
    { "id": "orders", "request": { "method": "GET", "endpoint": "https://shop.example.com/api/orders?status=open", "credential_id": "YOUR_SHOP_TOKEN_ID" } },
    { "id": "push", "request": {
        "method": "POST",
        "endpoint": "https://warehouse.example.com/v1/picklists",
        "credential_id": "YOUR_WAREHOUSE_TOKEN_ID",
        "headers": [{ "name": "Idempotency-Key", "value": "orders-{{ format(now(), \"YYYYMMDDHHmm\") }}" }],
        "body": "{ \"orders\": { \"{{#each $.orders}}\": { \"id\": \"{{ $.id }}\", \"sku\": \"{{ $.sku }}\", \"qty\": \"{{ $.quantity }}\" } } }" } }
  ]
}
```

## Securing the endpoint

Store a long random secret as a `bearer_token` credential, reference it with `credential_id`, and have the endpoint reject requests without `Authorization: Bearer <secret>`. If the work takes more than 10 seconds, return `202` and finish in the background.

## More ideas

- Warm a cache every 5 minutes: `*/5 * * * *`.
- Rotate logs or expire sessions nightly: `0 3 * * *`.
- Kick off a month-end export: `0 6 1 * *`, or on the last day: `0 18 L * *`.
- End a trial in 14 days with a one-time `run_at` instead of a delayed-job queue.

See the [docs](/docs#request-steps) for every request field, and [Use cases](/use-cases) for complete jobs.
