From 11cb8555e3ce9ecebf280feb3b886efa43ce0b8b Mon Sep 17 00:00:00 2001 From: Aniruddha Adak Date: Thu, 27 Aug 2026 19:21:45 +0530 Subject: [PATCH] feat(receivers): expose invalidRequestSignatureHandler on HTTPReceiver (closes #2156) Adds an optional invalidRequestSignatureHandler to HTTPReceiver and ExpressReceiver, mirroring the pattern introduced in AwsLambdaReceiver (#2154). The handler is invoked when a request fails signature verification, allowing custom logging/metrics before the 401 response is returned. --- src/receivers/HTTPModuleFunctions.ts | 38 +++++++++++++++++++++------- src/receivers/HTTPReceiver.ts | 6 +++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/receivers/HTTPModuleFunctions.ts b/src/receivers/HTTPModuleFunctions.ts index 239bb7c67..96d9c7254 100644 --- a/src/receivers/HTTPModuleFunctions.ts +++ b/src/receivers/HTTPModuleFunctions.ts @@ -85,15 +85,27 @@ export const parseAndVerifyHTTPRequest = async ( // Find the relevant request headers const signature = getHeader(req, 'x-slack-signature'); const requestTimestampSec = Number(getHeader(req, 'x-slack-request-timestamp')); - verifySlackRequest({ - signingSecret, - body: textBody, - headers: { - 'x-slack-signature': signature, - 'x-slack-request-timestamp': requestTimestampSec, - }, - logger: options.logger, - }); + try { + verifySlackRequest({ + signingSecret, + body: textBody, + headers: { + 'x-slack-signature': signature, + 'x-slack-request-timestamp': requestTimestampSec, + }, + logger: options.logger, + }); + } catch (err) { + if (options.invalidRequestSignatureHandler) { + options.invalidRequestSignatureHandler({ + rawBody: textBody, + signature, + requestTimestampSec, + request: req, + }); + } + throw err; + } // Checks have passed! Return the value that has a side effect (the buffered request) return bufferedReq; @@ -230,6 +242,14 @@ export interface RequestVerificationOptions { signingSecret: string; nowMilliseconds?: () => number; logger?: Logger; + invalidRequestSignatureHandler?: (args: ReceiverInvalidRequestSignatureHandlerArgs) => void; +} + +export interface ReceiverInvalidRequestSignatureHandlerArgs { + rawBody: string; + signature: string; + requestTimestampSec: number; + request: IncomingMessage; } // which handles errors occurred while dispatching a request diff --git a/src/receivers/HTTPReceiver.ts b/src/receivers/HTTPReceiver.ts index e1abba997..074051d97 100644 --- a/src/receivers/HTTPReceiver.ts +++ b/src/receivers/HTTPReceiver.ts @@ -97,6 +97,7 @@ export interface HTTPReceiverOptions { // NOTE: As we use setTimeout under the hood, this cannot be async unhandledRequestHandler?: (args: httpFunc.ReceiverUnhandledRequestHandlerArgs) => void; unhandledRequestTimeoutMillis?: number; + invalidRequestSignatureHandler?: (args: httpFunc.ReceiverInvalidRequestSignatureHandlerArgs) => void; } // All the available argument for OAuth flow enabled apps @@ -138,6 +139,8 @@ export default class HTTPReceiver implements Receiver { private signatureVerification: boolean; + private invalidRequestSignatureHandler?: (args: httpFunc.ReceiverInvalidRequestSignatureHandlerArgs) => void; + private app?: App; public requestListener: RequestListener; @@ -191,6 +194,7 @@ export default class HTTPReceiver implements Receiver { processEventErrorHandler = httpFunc.defaultProcessEventErrorHandler, unhandledRequestHandler = httpFunc.defaultUnhandledRequestHandler, unhandledRequestTimeoutMillis = 3001, + invalidRequestSignatureHandler = undefined, }: HTTPReceiverOptions) { verifySigningSecret(signingSecret, signatureVerification); // Initialize instance variables, substituting defaults for each value @@ -256,6 +260,7 @@ export default class HTTPReceiver implements Receiver { this.processEventErrorHandler = processEventErrorHandler; this.unhandledRequestHandler = unhandledRequestHandler; this.unhandledRequestTimeoutMillis = unhandledRequestTimeoutMillis; + this.invalidRequestSignatureHandler = invalidRequestSignatureHandler; // Assign the requestListener property by binding the unboundRequestListener to this instance this.requestListener = this.unboundRequestListener.bind(this); @@ -443,6 +448,7 @@ export default class HTTPReceiver implements Receiver { // If enabled: false, this method returns bufferedReq without verification enabled: this.signatureVerification, signingSecret: this.signingSecret, + invalidRequestSignatureHandler: this.invalidRequestSignatureHandler, }, req, );