feat: implement WhatsApp webhook authorization middleware for signature verification

This commit is contained in:
Nawaz Dhandala
2026-03-17 11:40:24 +00:00
parent 66e5f43c5d
commit fe76e946c0
2 changed files with 95 additions and 0 deletions

View File

@@ -12,6 +12,7 @@ import {
} from "Common/Types/WhatsApp/WhatsAppTemplates";
import WhatsAppStatus from "Common/Types/WhatsAppStatus";
import ClusterKeyAuthorization from "Common/Server/Middleware/ClusterKeyAuthorization";
import WhatsAppAuthorization from "Common/Server/Middleware/WhatsAppAuthorization";
import WhatsAppLogService from "Common/Server/Services/WhatsAppLogService";
import GlobalConfigService from "Common/Server/Services/GlobalConfigService";
import Express, {
@@ -371,6 +372,7 @@ router.get("/webhook", async (req: ExpressRequest, res: ExpressResponse) => {
router.post(
"/webhook",
WhatsAppAuthorization.isAuthorizedWhatsAppRequest,
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
const body: JSONObject = req.body as JSONObject;

View File

@@ -0,0 +1,93 @@
import {
ExpressResponse,
NextFunction,
OneUptimeRequest,
} from "../Utils/Express";
import Response from "../Utils/Response";
import BadDataException from "../../Types/Exception/BadDataException";
import GlobalConfig from "../../Models/DatabaseModels/GlobalConfig";
import GlobalConfigService from "../Services/GlobalConfigService";
import ObjectID from "../../Types/ObjectID";
import crypto from "crypto";
import logger from "../Utils/Logger";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
export default class WhatsAppAuthorization {
@CaptureSpan()
public static async isAuthorizedWhatsAppRequest(
req: OneUptimeRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> {
logger.debug("Starting WhatsApp webhook signature verification");
const signature: string | undefined = req.headers[
"x-hub-signature-256"
] as string | undefined;
if (!signature) {
logger.error(
"WhatsApp webhook request missing X-Hub-Signature-256 header.",
);
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"Missing X-Hub-Signature-256 header.",
),
);
}
const globalConfig: GlobalConfig | null =
await GlobalConfigService.findOneBy({
query: {
_id: ObjectID.getZeroObjectID().toString(),
},
props: {
isRoot: true,
},
select: {
metaWhatsAppAppSecret: true,
},
});
const appSecret: string | undefined =
globalConfig?.metaWhatsAppAppSecret?.trim() || undefined;
if (!appSecret) {
logger.error(
"Meta WhatsApp App Secret is not configured. Cannot verify webhook signature.",
);
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"Meta WhatsApp App Secret is not configured.",
),
);
}
const rawBody: string = req.rawBody || "";
const expectedSignature: string = `sha256=${crypto.createHmac("sha256", appSecret).update(rawBody).digest("hex")}`;
if (
!crypto.timingSafeEqual(
Buffer.from(expectedSignature) as Uint8Array,
Buffer.from(signature) as Uint8Array,
)
) {
logger.error("WhatsApp webhook signature verification failed.");
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"WhatsApp webhook signature verification failed.",
),
);
}
logger.debug("WhatsApp webhook signature verified successfully");
next();
}
}