How to Schedule a Vercel Serverless Function with Cronhooks

Rameez R.

Short answer: Vercel doesn't run your serverless functions on a schedule by itself. To schedule a Vercel function, you point an external webhook scheduler like Cronhooks at your function's URL and set a cron expression. Your function runs on whatever interval you choose — every hour, every day, every Monday at 9am — without any infrastructure to manage.

This guide shows you exactly how to do it, with working code.


Can Vercel functions run on a schedule?

Yes, but not natively on all plans. Vercel offers built-in cron jobs on Pro and Enterprise plans via vercel.json. If you're on the free Hobby plan, or you want more flexibility (custom intervals, per-environment scheduling, execution logs, failure alerts), using an external scheduler like Cronhooks is the better approach — and it works on any Vercel plan.


What we'll build

A Vercel API route that runs on a daily schedule. The example is a nightly data sync job — fetching data from an external API and storing it in a database. The same pattern applies to:

  • Sending scheduled emails or digests
  • Clearing expired sessions or stale cache
  • Generating daily reports
  • Running health checks against your own services
  • Posting scheduled messages to Slack or Teams

Prerequisites

  • A Vercel project (any plan)
  • A Cronhooks account — free tier works for this guide
  • Node.js 18+ locally

Step 1: Create the Vercel serverless function

Inside your project, create the file api/nightly-sync.ts:

import type { VercelRequest, VercelResponse } from "@vercel/node";

export default async function handler(req: VercelRequest, res: VercelResponse) {
  // Only allow POST requests
  if (req.method !== "POST") {
    return res.status(405).json({ error: "Method not allowed" });
  }

  // Verify the request is from Cronhooks using a shared secret
  const secret = req.headers["x-cronhooks-secret"];
  if (secret !== process.env.CRONHOOKS_SECRET) {
    return res.status(401).json({ error: "Unauthorized" });
  }

  try {
    // Your scheduled logic goes here
    // Example: fetch data from an external API
    const response = await fetch("https://api.example.com/data", {
      headers: { Authorization: `Bearer ${process.env.EXTERNAL_API_KEY}` },
    });

    if (!response.ok) {
      throw new Error(`External API returned ${response.status}`);
    }

    const data = await response.json();

    // Process and store the data...
    // await db.upsert(data);

    console.log(`Sync complete. Processed ${data.length} records.`);

    return res.status(200).json({
      success: true,
      processed: data.length,
      timestamp: new Date().toISOString(),
    });
  } catch (error) {
    console.error("Sync failed:", error);
    return res.status(500).json({
      success: false,
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

What this function does

  • Rejects anything that isn't a POST request
  • Checks for a shared secret in the x-cronhooks-secret header — requests without it get a 401
  • Runs your scheduled logic (replace the fetch example with your own)
  • Returns a structured JSON response that Cronhooks will log

Step 2: Set environment variables

Add the following to your .env.local file for local development:

CRONHOOKS_SECRET=any-long-random-string-you-choose
EXTERNAL_API_KEY=your-api-key

Then add the same variables to Vercel:

vercel env add CRONHOOKS_SECRET

Or set them in the Vercel dashboard under Project → Settings → Environment Variables.

The CRONHOOKS_SECRET is a shared secret you choose — it can be any long random string. You'll paste the same value into Cronhooks as a custom request header. This ensures only Cronhooks can trigger your function.


Step 3: Deploy to Vercel

vercel deploy --prod

Your function will be live at:

https://your-app.vercel.app/api/nightly-sync

Test it manually first:

curl -X POST https://your-app.vercel.app/api/nightly-sync \
  -H "Content-Type: application/json" \
  -H "x-cronhooks-secret: your-long-random-string"

You should see:

{ "success": true, "processed": 42, "timestamp": "2025-04-01T02:00:00.000Z" }

If you get a 401, double-check that your CRONHOOKS_SECRET environment variable is set in Vercel and that your deployment picked it up.


Step 4: Create a recurring schedule in Cronhooks

Log in to Cronhooks and create a new schedule.

Webhook URL:

https://your-app.vercel.app/api/nightly-sync

Method: POST

Headers:

Name Value
x-cronhooks-secret your-long-random-string
Content-Type application/json

Schedule: Recurring

Cron expression: 0 2 * * * (runs every day at 2:00 AM UTC)

Timezone: Pick your timezone — Cronhooks handles the UTC conversion.

Save the schedule. That's it.


Common cron expressions for Vercel functions

What you want Cron expression
Every day at midnight 0 0 * * *
Every day at 2am 0 2 * * *
Every hour 0 * * * *
Every 15 minutes */15 * * * *
Every Monday at 9am 0 9 * * 1
Weekdays at 8am 0 8 * * 1-5
First day of the month 0 0 1 * *

Not sure about your cron expression? Paste it into crontab.guru to verify it in plain English before saving.


Step 5: Verify the schedule is running

In Cronhooks, open your schedule and click Trigger now to fire it immediately. Check the execution log — you should see a 200 OK with your function's response body.

From this point, Cronhooks will automatically: - Call your function at the scheduled time - Log every execution with status code and response body - Send you an email or Slack alert if the function returns a non-2xx status or times out

You get full execution history without adding any logging infrastructure.


How to handle function timeouts

Vercel's default timeout for serverless functions is 10 seconds on the Hobby plan and 60 seconds on Pro. If your scheduled job does heavy lifting (large data processing, multiple API calls), keep these in mind:

For long-running jobs: - Break the work into smaller batches and schedule multiple runs - Or upgrade to Vercel Pro for the 60-second limit - Or move heavy processing to a background queue (e.g. Inngest, Trigger.dev) and just enqueue the job from your scheduled function

The scheduled function's job is to kick off work and return quickly — not necessarily to do all the work itself.


Frequently asked questions

Does this work on Vercel's free Hobby plan?

Yes. The Cronhooks approach works on any Vercel plan because Cronhooks calls your function over HTTP from outside Vercel. You don't need Vercel's native cron feature at all.

What's the difference between Cronhooks and Vercel's built-in cron?

Vercel's built-in cron (available on Pro and above) is tightly coupled to your deployment. Cronhooks is plan-agnostic, gives you detailed execution logs, supports failure alerts via email and Slack, and lets you manage schedules without touching your codebase or redeploying.

What happens if my function fails?

Cronhooks treats any non-2xx HTTP response as a failure. You'll receive an alert immediately and the failure will appear in your execution history. Cronhooks does not retry by default — if you want retries, you can handle that inside your function logic.

Can I pass data to my function from Cronhooks?

Yes. In the Cronhooks schedule configuration, you can set a custom JSON request body. Your function can read it from req.body. This is useful if you want to reuse one function endpoint with different parameters on different schedules.

How do I test this locally?

Run your Vercel dev server with vercel dev, then trigger the function manually with curl using your local URL:

curl -X POST http://localhost:3000/api/nightly-sync \
  -H "Content-Type: application/json" \
  -H "x-cronhooks-secret: your-long-random-string"

Is the x-cronhooks-secret header secure enough?

For most use cases, yes. The function URL is not publicly advertised and the secret is shared only between Vercel (as an env variable) and Cronhooks (as a header). For higher security requirements, you can additionally verify Cronhooks' HMAC signature — see the Cronhooks security docs.


Summary

To schedule a Vercel serverless function:

  1. Create an API route in /api that accepts POST and checks a secret header
  2. Deploy it to Vercel and set your secret as an environment variable
  3. Create a recurring schedule in Cronhooks pointing at your function's URL
  4. Add the secret header to the Cronhooks request so your function can verify it

Your function will run on schedule automatically. Cronhooks handles the timing, the execution logs, and the failure alerts.

Start scheduling for free on Cronhooks →


Related guides