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

如何解决WhatsApp Cloud API Webhook重复消息循环问题?

WhatsApp Cloud API Webhook 重复推送/响应问题排查与解决

问题背景

已通过个人手机号配置WhatsApp Cloud API Webhook,基于Node.js Express + TypeScript开发聊天机器人服务端。收到消息后根据类型触发响应逻辑,处理完成后通过POST graph.facebook.com/v16.0/${ID_CELL}/messages发送回复。按官方文档,服务端未返回200时WhatsApp会重复推送7天,但已确保出错时也返回200,仍出现重复消息推送、偶尔重复响应的情况。

相关代码

Webhook 接收处理代码

export const receiveMessage = async(req: Request, res: Response, next: NextFunction) => {

      let input = req.body.input[0];
      let changes = input.changes[0];
      let value = changes.value;
      let messageObject = value.messages;

      try{

          const messages: Message = messageObject[0];
          const from = message_object[0]?.from;

          //Type of message
          let typeMessage = messages.type;

          // First we record the log
          await registerEvents(entry, 200, "Receiving Webhook Whatsapp Api Cloud", null);

          // we validate that the message is not undefined
          if (messageObject === undefined) {
              console.log('no message found');
              res.sendStatus(200);
          }

          // we validate the type of messaging

          if (messageType === "text") {
              sendStatusMessage("Writing...", from);
              const data = await textProcess(messages, from);
              SendMessageWhatsApp(data);
              res.sendStatus(200);
          }

          if (messageType === "audio") {
              console.log('is audio');
              const data = await audioProcess(messages, from);
              SendMessageWhatsApp(data);
              res.sendStatus(200);
          }

          if (messageType === "button") {
              const data = await buttonProcess(messages, from);
              SendMessageWhatsApp(data);
              res.sendStatus(200);
          }

          if (messageType === "interactive") {
              const data = await interactiveProcess(messages, from);
              SendMessageWhatsApp(data);
              res.sendStatus(200);
          }



      } catch (error) {

          await registerEvents(input, 500, "Webhook Whatsapp Api Cloud Error", error)
          // Avoid lopp webhook
          console.log('An internal error occurred on the server but 200 should always be sent');
          res.sendStatus(200)
      }

}

消息发送代码

function SendMessageWhatsApp(message: any) {
      constant data = JSON.stringify(message);

      constant options = {
          host: "graph.facebook.com",
          path: `/v16.0/${ID_CELL}/messages`,
          method: "POST",
          body: data,
          headers: {
              "Content-type": "application/json",
              Authorization: `Bearer ${TOKEN_CLOUD_API_WSP}`
          }
      };
      const req = https.request(options, res => {
          res.on("data", d => {
              process.stdout.write(d);
          });
      });

      req.on("error", error => {
          console.error(error);
          supabase.from('records').insert([
              {
                  message: error,
                  status: 500,
                  task: "Error sending message to Whatsapp",
                  error: null
              }
          ]);
      });

      req.write(data);
      req.end();
}

问题排查与解决方案

1. 立即返回200,异步处理业务逻辑

当前代码在执行await textProcess等耗时操作后才返回200,若业务处理耗时超过WhatsApp的超时阈值(通常10秒左右),平台会判定请求超时并重新推送消息。必须先返回200确认接收,再异步处理消息响应逻辑。

修改示例:

export const receiveMessage = async(req: Request, res: Response, next: NextFunction) => {
      // 第一步:立即返回200,确认Webhook消息接收
      res.sendStatus(200);

      let input = req.body.input[0];
      let changes = input.changes[0];
      let value = changes.value;
      let messageObject = value.messages;

      try{
          if (!messageObject || messageObject.length === 0) {
              console.log('no message found');
              return;
          }

          const messages: Message = messageObject[0];
          const from = messages.from; // 修复变量名拼写错误
          const messageType = messages.type;

          await registerEvents(input, 200, "Receiving Webhook Whatsapp Api Cloud", null);

          // 异步处理业务逻辑,不阻塞响应
          switch(messageType) {
              case "text":
                  sendStatusMessage("Writing...", from);
                  const textData = await textProcess(messages, from);
                  SendMessageWhatsApp(textData);
                  break;
              case "audio":
                  console.log('is audio');
                  const audioData = await audioProcess(messages, from);
                  SendMessageWhatsApp(audioData);
                  break;
              case "button":
                  const buttonData = await buttonProcess(messages, from);
                  SendMessageWhatsApp(buttonData);
                  break;
              case "interactive":
                  const interactiveData = await interactiveProcess(messages, from);
                  SendMessageWhatsApp(interactiveData);
                  break;
              default:
                  console.log('Unsupported message type:', messageType);
          }
      } catch (error) {
          await registerEvents(input, 500, "Webhook Whatsapp Api Cloud Error", error)
          console.log('An internal error occurred on the server:', error);
      }
}

2. 修复变量名错误

原代码中const from = message_object[0]?.from;存在变量名拼写错误(应为messages.from),会导致from为undefined,后续业务逻辑出错,可能引发重复推送。

3. 消息去重:利用WhatsApp唯一消息ID

每条WhatsApp消息都有唯一的messages.id字段,可将已处理的消息ID存入缓存(如Redis)或数据库,处理前先检查是否已处理过,避免重复响应。

示例逻辑:

// 假设使用Redis缓存,需先安装redis库并配置客户端
import { createClient } from 'redis';
const redisClient = createClient();

// 在处理消息前添加去重逻辑
const messageId = messages.id;
const isProcessed = await redisClient.get(`whatsapp:processed:${messageId}`);
if (isProcessed) {
    console.log('Message already processed:', messageId);
    return;
}
// 设置缓存,过期时间设为7天(对应WhatsApp重复推送周期)
await redisClient.setEx(`whatsapp:processed:${messageId}`, 7*24*3600, '1');

4. 优化消息发送函数的错误处理

原SendMessageWhatsApp函数未正确处理HTTP响应状态,若发送请求失败(如Token过期、权限不足),不会记录完整错误信息,也没有重试机制,可能导致用户未收到回复,而重复收到推送时会再次发送。

修改后的发送函数:

async function SendMessageWhatsApp(message: any) {
    try {
        const data = JSON.stringify(message);
        const options = {
            host: "graph.facebook.com",
            path: `/v16.0/${ID_CELL}/messages`,
            method: "POST",
            headers: {
                "Content-type": "application/json",
                Authorization: `Bearer ${TOKEN_CLOUD_API_WSP}`
            }
        };

        return new Promise<void>((resolve, reject) => {
            const req = https.request(options, res => {
                let responseBody = '';
                res.on("data", d => {
                    responseBody += d;
                });

                res.on("end", () => {
                    // 检查响应状态码
                    if (res.statusCode && res.statusCode >= 400) {
                        const error = new Error(`Failed to send message: ${res.statusCode} - ${responseBody}`);
                        console.error(error);
                        supabase.from('records').insert([
                            {
                                message: error.message,
                                status: res.statusCode,
                                task: "Error sending message to Whatsapp",
                                error: responseBody
                            }
                        ]);
                        reject(error);
                    } else {
                        console.log('Message sent successfully:', responseBody);
                        resolve();
                    }
                });
            });

            req.on("error", error => {
                console.error('Request error:', error);
                supabase.from('records').insert([
                    {
                        message: error.message,
                        status: 500,
                        task: "Error sending message to Whatsapp",
                        error: JSON.stringify(error)
                    }
                ]);
                reject(error);
            });

            req.write(data);
            req.end();
        });
    } catch (error) {
        console.error('SendMessageWhatsApp error:', error);
        supabase.from('records').insert([
            {
                message: (error as Error).message,
                status: 500,
                task: "Error sending message to Whatsapp",
                error: JSON.stringify(error)
            }
        ]);
        throw error;
    }
}

5. 检查Webhook订阅事件

确保Webhook仅订阅messages事件,若订阅了message_status等其他事件,可能会收到重复的状态通知,误判为新消息处理。


内容的提问来源于stack exchange,提问作者Izlia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 15:12:02