使用Google Cloud Function配置Meta WhatsApp Cloud API Webhook验证失败
在Google Cloud Function中搭建Webhook用于接收WhatsApp Business API消息并转发至Front平台时,Meta for WhatsApp配置Webhook时出现错误:The callback URL or verify token couldn't be validated. Please verify the provided information or try again later.
已确认WhatsApp Business API凭证、Webhook URL配置正确,Google Cloud Function已部署可访问,但查看日志发现验证Webhook时出现:Received message with missing contacts or messages property. Skipping message processing,尝试返回200状态码仍无法解决。
相关代码如下:
const axios = require('axios'); const FRONT_API_TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzY29wZXMiOlsicHJvdmlzaW9uaW5nIiwicHJpdmF0ZToqIiwic2hhcmVkOioiXSwiaWF0IjoxNjc5NTE0MDU1LCJpc3MiOiJmcm9udCIsInN1YiI6ImI4MGUzZDExODQyMDUzZTk5OGE0IiwianRpIjoiYmM5NzNlNGQyZTA3YTAzMiJ9.7LBqJ5Kw3O65c4GttZuh4K2Zt7fkGIIq9yI96l06TJ8'; const FRONT_CUSTOM_CHANNEL_WEBHOOK_URL = 'https://api2.frontapp.com/channels/cha_ak6s0/incoming_messages'; const VERIFY_TOKEN = 'whatsappfronttoken'; const handleVerification = (req, res) => { const queryToken = req.query.verify_token; console.log('Verification request received:', req.query); if (queryToken === VERIFY_TOKEN) { res.send(req.query.challenge); } else { console.error('Invalid verify token:', queryToken); res.sendStatus(403); } }; exports.whatsappHandler = async (req, res) => { if (req.query.verify_token) { handleVerification(req, res); } else { const message = req.body; if (!message.contacts || !message.messages) { console.warn('Received message with missing contacts or messages property. Skipping message processing.'); res.sendStatus(200); return; } // Extract relevant information from the WhatsApp message const sender = message.contacts[0].profile.name || message.contacts[0].wa_id; const text = message.messages[0].text.body; // Format the message for Front's custom channel webhook URL const formattedMessage = { sender: { name: sender, handle: sender, }, subject: 'WhatsApp Message', body: text, body_format: 'markdown', }; // Forward the message to Front's custom channel webhook URL try { await axios.post(FRONT_CUSTOM_CHANNEL_WEBHOOK_URL, formattedMessage, { headers: { 'Authorization': `Bearer ${FRONT_API_TOKEN}`, }, }); res.sendStatus(200); } catch (error) { console.error(error); res.sendStatus(500); } } };
Meta的Webhook验证请求是POST请求,不会携带verify_token查询参数,而是将验证信息(hub.mode、hub.verify_token、hub.challenge)放在请求体中。当前代码仅在req.query.verify_token存在时才处理验证逻辑,导致验证请求被误判为普通消息请求,进入后续分支后因缺少contacts和messages字段触发警告,最终虽然返回200,但没有正确响应Meta的验证要求。
修改whatsappHandler函数,优先通过请求体中的hub.mode字段判断是否为验证请求,替换原有的查询参数判断逻辑:
exports.whatsappHandler = async (req, res) => { // 处理Meta的Webhook验证请求 const { hub } = req.body; if (hub?.mode === 'subscribe' && hub?.verify_token === VERIFY_TOKEN) { res.send(hub.challenge); return; } // 处理普通的WhatsApp消息请求 const message = req.body; if (!message.contacts || !message.messages) { console.warn('Received message with missing contacts or messages property. Skipping message processing.'); res.sendStatus(200); return; } // 原有的消息转发逻辑保持不变 const sender = message.contacts[0].profile.name || message.contacts[0].wa_id; const text = message.messages[0].text.body; const formattedMessage = { sender: { name: sender, handle: sender, }, subject: 'WhatsApp Message', body: text, body_format: 'markdown', }; try { await axios.post(FRONT_CUSTOM_CHANNEL_WEBHOOK_URL, formattedMessage, { headers: { 'Authorization': `Bearer ${FRONT_API_TOKEN}`, }, }); res.sendStatus(200); } catch (error) { console.error(error); res.sendStatus(500); } };
额外注意事项:
- 确保Google Cloud Function的触发方式为HTTP触发,允许所有请求方法(尤其是POST)
- 验证请求的响应需直接返回
hub.challenge的原始内容,不能添加额外格式或状态码包装
内容的提问来源于stack exchange,提问作者nvercher

