First, are you sure you need to make network requests between your own functions? Since you own your backend, can you just make /api/cron-process-notifications.js and /api/notification-trigger call the same triggerNotification() function, rather than making one endpoint fetch the other? That would bypass all need for networking
Otherwise, how are you authenticating your second endpoint? If it’s code like this, you should be able to log both the authHeader and the CRON_SECRET side by side and see which one is coming up wrong
export function GET(request: NextRequest) {
const authHeader = request.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response('Unauthorized', {
status: 401,
});
}
if the 401 error is throwing before it even gets to that step, then make sure ${baseUrl}/api/notification-trigger is the correct path. If you try to curl that one directly, does that work? That would narrow down whether this is a function → function communication issue or just an issue with the second function.
If you’re in a protected deployment (like a preview deploy where you must be logged into Vercel to access it) then it will reject unauthenticated requests with a 401, which could cause what you’re seeing. You can add the x-vercel-bypass-automation header to let your request skip that login screen
That option would require a refactor of the application. However, even if I did that, that wouldn’t explain why I get a 401 if I try to hit all other two API endpoints with my CRON_SECRET.
First endpoint: Scheduled trigger (runs on a time schedule) (Works with CRON_SECRET)
Second endpoint: Finder (looks for work to be done) (Fails curl command with 401)
Third endpoint: Processor (does the actual work) (Fails curl command with 401)
Additionally, I’ve already followed your suggestion and logged the header on the other jobs, and when I check the Vercel logs, I can confirm that the bearer $cron_secret matches across all three jobs. They literally use the same authentication script.
Been troubleshooting this one for around a week or more now, finally reached out to support last night and they said my cron secret is wrong, but again that wouldn’t explain why I can curl into one and not the other.
it’s not just about logging the header (which you’ve verified is correct) but also the CRON_SECRET you’re comparing it to. I’m suspecting that CRON_SECRET may only exist in the scheduled cron function and might be undefined in the others, which would cause the mismatch
If it does exist, and you can prove that it matches in all of them like below, then it’s impossible for that if() block to execute, which means the 401 response you’re getting is actually coming from somewhere else
I figured it out. it’s because vercel serverless lives on another domain and they cannot access cron_secrets.
Using system variables is one part of the problem. The other part of the problem is that Vercel serverless doesn’t use the domain name as the originator but the vercel domain + app build url convention that they created.
I had to build a method to hardcode my urls when originating from cron jobs.