How to Schedule a Vercel Serverless Function with Cronhooks
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
POSTrequest - Checks for a shared secret in the
x-cronhooks-secretheader — requests without it get a401 - 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:
- Create an API route in
/apithat acceptsPOSTand checks a secret header - Deploy it to Vercel and set your secret as an environment variable
- Create a recurring schedule in Cronhooks pointing at your function's URL
- 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 →