| 80 | } |
| 81 | |
| 82 | private handlePost(req: IncomingMessage, res: ServerResponse): void { |
| 83 | const chunks: Buffer[] = []; |
| 84 | req.on("data", (c) => chunks.push(c as Buffer)); |
| 85 | req.on("end", () => { |
| 86 | const rawBuf = Buffer.concat(chunks); |
| 87 | const raw = rawBuf.toString("utf8"); |
| 88 | const sig = req.headers["x-hub-signature-256"]; |
| 89 | if ( |
| 90 | !this.verifySignature(rawBuf, typeof sig === "string" ? sig : undefined) |
| 91 | ) { |
| 92 | res.statusCode = 401; |
| 93 | res.end(); |
| 94 | return; |
| 95 | } |
| 96 | // Ack immediately; Meta retries non-200 and can disable a flapping webhook. |
| 97 | res.statusCode = 200; |
| 98 | res.end(); |
| 99 | let body: WebhookBody; |
| 100 | try { |
| 101 | body = JSON.parse(raw) as WebhookBody; |
| 102 | } catch { |
| 103 | return; |
| 104 | } |
| 105 | void this.args.onEvent(body).catch((err) => { |
| 106 | console.error("[whatsapp] onEvent failed:", err); |
| 107 | }); |
| 108 | }); |
| 109 | } |
| 110 | |
| 111 | private verifySignature(raw: Buffer, signature: string | undefined): boolean { |
| 112 | if (!signature || !signature.startsWith("sha256=")) return false; |