You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js实现WhatsApp云API Webhook会话超时及手机号存储问题

问题分析与修复方案

你的代码无法正确存储手机号主要源于几个关键错误,以下是针对性的修复步骤:

核心问题点

  • 会话存储变量名不一致:定义的是const session = {},但后续代码全用复数sessions,导致会话数据根本没存在正确的存储对象里。
  • 手机号提取路径错误:中间件里直接取req.body.phoneNumber,但WhatsApp Webhook的用户手机号实际嵌套在req.body.entry[0].changes[0].value.messages[0].from路径下。
  • 缺少JSON解析中间件:Express默认不会自动解析POST请求的JSON Body,导致req.body为空,无法提取任何有效数据。
  • UUID模块重复加载:每次生成Session ID时都重新require模块,既低效又不符合Node.js模块加载规范。

修复后的完整代码

const express = require('express');
const uuid = require('uuid'); // 提前导入UUID模块,避免重复加载
const app = express();

// 必须添加JSON解析中间件,放在所有路由之前
app.use(express.json());

// 统一会话存储变量名
const sessions = {};

function sessionMiddleware(sessionTimeout) {
  return function(req, res, next) {
    // 正确从WhatsApp Webhook Payload中提取用户手机号,添加可选链避免报错
    let phoneNumber = null;
    if (req.body.entry?.[0]?.changes?.[0]?.value?.messages?.[0]) {
      phoneNumber = req.body.entry[0].changes[0].value.messages[0].from;
    }

    // 未获取到手机号时(如Webhook验证请求、非消息类事件),直接跳过会话处理
    if (!phoneNumber) {
      req.session = null;
      return next();
    }

    // 根据手机号查找已有会话ID
    const sessionId = getSessionIdFromPhoneNumber(phoneNumber);
    if (sessionId && sessions[sessionId]) {
      const session = sessions[sessionId];
      const currentTime = new Date().getTime();
      if (currentTime - session.lastAccessTime > sessionTimeout) {
        // 会话超时,删除会话
        delete sessions[sessionId];
        req.session = null;
      } else {
        // 更新会话最后访问时间
        session.lastAccessTime = currentTime;
        req.session = session.data;
      }
    } else {
      // 创建新会话
      const newSessionId = generateSessionId();
      const currentTime = new Date().getTime();
      const newSession = {
        sessionId: newSessionId,
        creationTime: currentTime,
        lastAccessTime: currentTime,
        phoneNumber: phoneNumber,
        data: {}
      };
      sessions[newSessionId] = newSession;
      req.session = newSession.data;
    }
    next();
  }
}

function getSessionIdFromPhoneNumber(phoneNumber) {
  // 简化遍历逻辑
  for (const sessionId of Object.keys(sessions)) {
    const session = sessions[sessionId];
    if (session.phoneNumber === phoneNumber) {
      return sessionId;
    }
  }
  return null;
}

function generateSessionId() {
  return uuid.v4();
}

app.post("/webhook", sessionMiddleware(30 * 60 * 1000), (req, res) => {
  const phone_number_id = req.body.entry[0].changes[0].value.metadata.phone_number_id;
  const from = req.body.entry[0].changes[0].value.messages[0].from;

  // 这里添加你的业务逻辑处理代码

  // 必须返回200状态码给WhatsApp,否则会重复触发Webhook
  res.status(200).send("OK");
});

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

额外注意事项

  • 必须给WhatsApp Webhook返回200状态码,否则Meta会判定服务不可用,重复发送请求。
  • 可添加非消息事件的处理逻辑(如状态更新),避免无效的会话创建。
  • 生产环境建议用Redis等持久化存储替代内存中的sessions对象,防止服务器重启后会话丢失。

内容的提问来源于stack exchange,提问作者Dendy Tiawan Putra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 03:32:44