Next.js Serverless函数异常:Vercel部署后需手动触发后续请求
WhatsApp Webhook部署Vercel后异步请求失效问题解决
问题背景
我有一个接收WhatsApp Webhook消息的端点,逻辑是先异步调用另一个内部端点,再向WhatsApp返回200状态码。本地运行一切正常,但部署到Vercel后,只有第一个端点能触发,后续的内部请求必须手动Ping对应的端点才会执行。尝试加日志但Vercel生产环境调试困难,效果不好。相关代码如下:
const conversation = await prisma.conversation.findFirst({ where: { senderId: message.senderId, }, }); console.log( `${process.env.BASE_URL}/api/whatsapp/services/preValidation` ); if (!conversation?.verify || !conversation) { console.log("member is not verified yet"); axios.post( `${process.env.BASE_URL}/api/whatsapp/services/preValidation`, { conversation: conversation, message: message, recipientId: teamId, source: "whatsapp", } ); return new Response("message is not verified yet", { status: 200, }); } axios.post( `${process.env.BASE_URL}/api/facebook/services/handleMessages`, { conversation: conversation, message: message, recipientId: teamId, } ); return new Response("EVENT_RECEIVED", { status: 200 });
核心原因
- Vercel无服务器函数进程终止机制:当函数返回响应后,Vercel会立刻终止进程,未完成的异步操作会被强制中断。当前代码里
axios.post是异步执行的,但没等它完成就直接返回了200,导致后续请求还没来得及跑就被砍了。 - 函数冷启动休眠:Vercel的函数长时间没调用会进入休眠状态,首次调用需要冷启动。异步请求触发时,目标函数可能还在休眠,导致请求无法执行,手动Ping相当于唤醒函数完成冷启动。
解决方案
方案1:等待异步请求完成后再返回响应
给axios.post加上await,确保请求执行完再返回200。同时要设置超时时间,避免WhatsApp Webhook因为超时重复推送消息,还要捕获错误避免进程崩溃。
修改后代码:
const conversation = await prisma.conversation.findFirst({ where: { senderId: message.senderId, }, }); console.log(`${process.env.BASE_URL}/api/whatsapp/services/preValidation`); if (!conversation?.verify || !conversation) { console.log("member is not verified yet"); try { // 等待请求完成,设置10秒超时适配WhatsApp Webhook限制 await axios.post( `${process.env.BASE_URL}/api/whatsapp/services/preValidation`, { conversation: conversation, message: message, recipientId: teamId, source: "whatsapp", }, { timeout: 10000 } ); } catch (error) { console.error("preValidation请求失败:", error); // 即使请求失败,也要返回200给WhatsApp,防止重复推送 } return new Response("message is not verified yet", { status: 200 }); } try { await axios.post( `${process.env.BASE_URL}/api/facebook/services/handleMessages`, { conversation: conversation, message: message, recipientId: teamId, }, { timeout: 10000 } ); } catch (error) { console.error("handleMessages请求失败:", error); } return new Response("EVENT_RECEIVED", { status: 200 });
方案2:用Vercel Queue处理后台异步任务
如果异步请求执行时间较长(超过15秒),超过WhatsApp Webhook的超时限制,就用Vercel Queue把任务放到后台执行。主函数立即返回200,保证WhatsApp不会重试,同时后台任务能正常跑完。
步骤1:定义队列任务
创建队列处理路由app/api/queues/process-message/route.ts:
import { Queue } from "@vercel/queue"; import axios from "axios"; const queue = new Queue("process-message"); export async function POST(request: Request) { const { type, payload } = await request.json(); try { if (type === "preValidation") { await axios.post( `${process.env.BASE_URL}/api/whatsapp/services/preValidation`, payload ); } else if (type === "handleMessages") { await axios.post( `${process.env.BASE_URL}/api/facebook/services/handleMessages`, payload ); } } catch (error) { console.error("队列任务执行失败:", error); throw error; // 触发自动重试 } return new Response("任务执行完成", { status: 200 }); }
步骤2:修改Webhook端点加入队列
const conversation = await prisma.conversation.findFirst({ where: { senderId: message.senderId, }, }); console.log(`${process.env.BASE_URL}/api/whatsapp/services/preValidation`); if (!conversation?.verify || !conversation) { console.log("member is not verified yet"); // 将任务加入队列 await fetch(`${process.env.BASE_URL}/api/queues/process-message`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ type: "preValidation", payload: { conversation: conversation, message: message, recipientId: teamId, source: "whatsapp", }, }), }); return new Response("message is not verified yet", { status: 200 }); } await fetch(`${process.env.BASE_URL}/api/queues/process-message`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ type: "handleMessages", payload: { conversation: conversation, message: message, recipientId: teamId, }, }), }); return new Response("EVENT_RECEIVED", { status: 200 });
方案3:优化冷启动问题
- 如果你是Vercel Pro计划用户,可以开启函数预热,设置最小实例数,避免函数休眠。
- 将频繁调用的内部端点合并,减少冷启动触发次数。
内容的提问来源于stack exchange,提问作者AIceberg
相关产品推荐
相关产品推荐

