NestJS集成Twilio Conversation API时onMessageAdded Webhook未触发
我用NestJS搭建后端,集成Twilio Conversation API开发实时聊天应用。已配置Webhook地址,期望聊天新增消息时触发onMessageAdded事件并通过POST请求调用该Webhook,同时使用ngrok做本地请求转发。目前消息可成功发送,但onMessageAdded事件始终无法触发后端接口,处于开发环境。
相关代码
启动代码(main.ts)
async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors(); const twilioChatService = app.get(TwilioChatService); await twilioChatService.setupConversationsWebhook(ENV['TWILIO_CHAT_SID'], 'https://lab-amazing-seahorse.ngrok-free.app/'); app.useGlobalPipes(new ValidationPipe()); app.useGlobalPipes( new ValidationPipe({ whitelist: true, }), ); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); app.useGlobalPipes(new ValidationPipe()); app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector))); await app.listen(ENV['PORT'], () => console.log(`Server is running on port ${ENV['PORT']}`), ); } bootstrap();
TwilioChatService
private readonly twilioClient: Twilio; constructor() { this.twilioClient = new Twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); } async setupConversationsWebhook(conversationSid: string, webhookUrl: string) { try { this.twilioClient.conversations.v1.conversations(conversationSid).webhooks(ENV['TWILIO_WEBHOOK_SID']).update({ 'configuration.method': 'POST', 'configuration.filters': ['onMessageAdded'], 'configuration.url': webhookUrl, }).then(webhook => console.log(webhook.sid)); console.log(`Conversations webhook set up at ${webhookUrl}`); } catch (error) { console.error('Error setting up Conversations webhook:', error.message); } }
ChatController(chat.controller.ts)
import { Controller, Post, Body, Req, Get, Param } from '@nestjs/common'; import { ChatService } from './chat.service'; @Controller('chat') export class ChatController { constructor(private readonly chatService: ChatService) { } @Post('webhook') async handleWebhook(@Body() eventData: any): Promise<any> { console.log(" === handleWebhook called ==="); console.log("Received webhook event:", eventData); await this.chatService.handleWebhookEvent(eventData); } }
ChatService(chat.service.ts)
async handleWebhookEvent(eventData: any): Promise<void> { const eventType = eventData.EventType; if (eventType === 'onMessageAdded') { console.log("Called onMessageSent()"); const conversationSid = eventData.ConversationSid; const messageSid = eventData.MessageSid; // Send HTTP request when a new message is sent console.log("=== GOT the Message ===", messageSid); this.sendMessage(conversationSid, eventData.SenderIdentity, eventData.Body); } }
ChatModule(chat.module.ts)
@Module({ imports: [TwilioModule.forRoot({ accountSid: process.env.TWILIO_ACCOUNT_SID, authToken: process.env.TWILIO_AUTH_TOKEN })], controllers: [ChatController], providers: [TwilioChatService, ChatService], }) export class ChatModule { }
已确认的配置/日志
- Twilio Webhook地址与请求方式配置正确
- Twilio端已勾选
onMessageAdded事件 - 前端消息发送成功,Twilio对话消息记录正常
- ngrok本地转发服务运行中
排查与解决方案
补全Webhook URL路径
当前代码传入的Webhook URL为https://lab-amazing-seahorse.ngrok-free.app/,但Controller的路由是/chat/webhook,完整URL应为https://lab-amazing-seahorse.ngrok-free.app/chat/webhook。路径缺失会导致Twilio无法找到正确的接口端点。确保Webhook更新操作成功
原代码用.then()处理异步操作但未捕获更新失败场景,改用await并添加详细日志,确认Webhook是否更新成功:async setupConversationsWebhook(conversationSid: string, webhookUrl: string) { try { const webhook = await this.twilioClient.conversations.v1.conversations(conversationSid) .webhooks(ENV['TWILIO_WEBHOOK_SID']) .update({ 'configuration.method': 'POST', 'configuration.filters': ['onMessageAdded'], 'configuration.url': webhookUrl, }); console.log(`Webhook updated successfully, SID: ${webhook.sid}`); console.log(`Conversations webhook set up at ${webhookUrl}`); } catch (error) { console.error('Error setting up Conversations webhook:', error); } }验证Twilio控制台的Webhook配置
登录Twilio控制台,进入目标Conversation的Webhook设置页面,确认:- 事件过滤器明确包含
onMessageAdded - 请求方法设置为POST
- URL与代码中配置的完整路径一致
- 事件过滤器明确包含
检查ngrok转发有效性
手动访问https://lab-amazing-seahorse.ngrok-free.app/chat/webhook,确认后端能接收请求(可临时添加GET接口测试)。若无法访问,检查ngrok是否正常运行,或是否有防火墙/代理拦截请求。查看Twilio Webhook失败日志
登录Twilio控制台,进入Monitor > Events,筛选Webhook相关事件,查看是否有失败记录,根据错误信息(如超时、4XX/5XX状态码)定位问题。调整ValidationPipe配置
代码中重复添加ValidationPipe且开启whitelist: true,Twilio的Webhook请求体可能包含未定义的字段,导致请求被拦截。建议:- 移除重复的
ValidationPipe配置 - 为Webhook接口单独关闭白名单校验:
@Post('webhook') @UsePipes(new ValidationPipe({ whitelist: false })) async handleWebhook(@Body() eventData: any): Promise<any> { // ... }
- 移除重复的
内容的提问来源于stack exchange,提问作者Muhammad Umair Qaisar

