/opt/mawid/apps/api/src/whatsapp
NameSizeModeActions
owner-notifier.service.ts17450644editdlrm
wa-core.module.ts13520644editdlrm
wa-http.client.ts17360644editdlrm
wa-inbound.service.spec.ts50660644editdlrm
wa-inbound.service.ts54310644editdlrm
wa-outbound.worker.ts27230644editdlrm
wa-queue.ts4470644editdlrm
wa-sender.service.ts49590644editdlrm
wa-signature.spec.ts12640644editdlrm
wa-signature.ts6620644editdlrm
wa-types.ts16830644editdlrm
wa-webhook.controller.spec.ts26840644editdlrm
wa-webhook.controller.ts23080644editdlrm
wa-window.spec.ts8600644editdlrm
wa-window.ts5090644editdlrm
whatsapp.module.ts5790644editdlrm
Edit: /opt/mawid/apps/api/src/whatsapp/wa-webhook.controller.ts (2308B)
import { BadRequestException, Controller, ForbiddenException, Get, HttpCode, Logger, Post, Query, Req, type RawBodyRequest, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Throttle } from '@nestjs/throttler'; import type { FastifyRequest } from 'fastify'; import { Public } from '../auth/public.decorator'; import { WaInboundService } from './wa-inbound.service'; import { verifyWaSignature } from './wa-signature'; import type { WaWebhookPayload } from './wa-types'; // Meta bursts webhook deliveries — allow more headroom than the API default. @Throttle({ default: { ttl: 60_000, limit: 600 } }) @Public() @Controller('webhooks/whatsapp') export class WaWebhookController { private readonly logger = new Logger(WaWebhookController.name); constructor( private readonly config: ConfigService, private readonly inbound: WaInboundService, ) {} /** Meta subscription verification handshake. */ @Get() verify( @Query('hub.mode') mode?: string, @Query('hub.verify_token') token?: string, @Query('hub.challenge') challenge?: string, ): string { if (mode === 'subscribe' && token === this.config.get('WA_WEBHOOK_VERIFY_TOKEN') && challenge) { return challenge; } throw new ForbiddenException('Webhook verification failed'); } @Post() @HttpCode(200) // Meta expects 200, not Nest's default 201 for POST async receive(@Req() req: RawBodyRequest): Promise<{ received: true }> { const signature = req.headers['x-hub-signature-256'] as string | undefined; const appSecret = this.config.get('WA_APP_SECRET') ?? ''; if (!verifyWaSignature(req.rawBody, signature, appSecret)) { throw new ForbiddenException('Invalid webhook signature'); } const payload = req.body as WaWebhookPayload; if (payload?.object !== 'whatsapp_business_account') { throw new BadRequestException('Unexpected webhook object'); } // Ack immediately — agent turns take seconds of LLM calls, and holding // Meta's webhook open past its timeout triggers redelivery storms. void Promise.resolve(this.inbound.process(payload)).catch((err) => this.logger.error(`Webhook processing failed: ${String(err)}`), ); return { received: true }; } }