Hello
First time poster and first time using RedwoodJS.
I’ve been working on a Slack integration application and need to receive their events API. I couldn’t find another example of the Slack Events API in the Redwood Webhook docs so I thought I’d share what I did because there are a few quirks with the Slack signature you want to be aware of.
I’d love to hear any improvements anyone would like to share. Otherwise I simply wanted to put this out there to hopefully accelerate someone else’s development time.
Slack uses the same SHA256 Verifier as GitHub and Discourse but you need a signature transformer create the expected format for the signature verifier function because Slack doesn’t include the algorithm.
import type { APIGatewayEvent } from 'aws-lambda'
import {
verifyEvent,
VerifyOptions,
WebhookVerificationError,
} from '@redwoodjs/api/dist/webhooks'
import { logger as webhookLogger } from 'src/lib/logger'
export const handler = async (event: APIGatewayEvent) => {
const logger = webhookLogger.child({ webhook: 'slack' })
logger.info('Invoked slackEvents function')
try {
const slackTimestamp = event.headers['x-slack-request-timestamp'] || ''
const options = {
signatureHeader: 'x-slack-signature',
// the timestamp comes in seconds format so convert to miliseconds
eventTimestamp: Number.parseInt(slackTimestamp, 10) * 1000,
signatureTransformer: (signature: string) => {
// Slack passes a signature that starts with a version
// example: v0=hash
const [version, signatureHash] = signature.split('=')
// Slack only supports version v0 currently
if (version === 'v0') {
// verifyEvent expects the signature to include the algorithm
return `sha256=${signatureHash}`
}
},
} as VerifyOptions
const [signatureVersion] = (event.headers['x-slack-signature'] || '').split(
'='
)
if (signatureVersion !== 'v0') {
throw new WebhookVerificationError(
`${signatureVersion}: unknown signature version`
)
}
verifyEvent('sha256Verifier', {
event,
secret: process.env.SLACK_SIGNING_SECRET,
payload: `${signatureVersion}:${slackTimestamp}:${event.body}`,
options,
})
} catch (error) {
if (error instanceof WebhookVerificationError) {
logger.warn('Unauthorized')
return {
statusCode: 401,
}
} else {
logger.error({ error }, error.message)
return {
headers: {
'Content-Type': 'application/json',
},
statusCode: 500,
body: JSON.stringify({
error: error.message,
}),
}
}
}
}