@bernierllc/slack-list-bridge
v0.3.2
Published
Bridge between Slack list events and arbitrary handlers with signature verification
Readme
@bernierllc/slack-list-bridge
Bridge between Slack list events (via Workflow Builder webhooks) and arbitrary handlers. Receives POST requests from Slack, validates signatures, parses payloads, and calls registered handlers.
Features
- HTTP endpoint for Slack webhooks (Express-based)
- Slack signature verification using signing secret
- Payload parsing with Zod schemas for type safety
- Type-safe event objects
- Helper functions to update Slack list items
- Comprehensive error handling
Installation
npm install @bernierllc/slack-list-bridgeUsage
Basic Example
import { createSlackListBridge } from '@bernierllc/slack-list-bridge';
import { WebClient } from '@slack/web-api';
const bridge = createSlackListBridge({
signingSecret: process.env.SLACK_SIGNING_SECRET!,
botToken: process.env.SLACK_BOT_TOKEN!,
port: 3000,
path: '/slack/list-webhook',
async handler(event) {
console.log('Received list event:', event);
// Access event properties
console.log('List ID:', event.listId);
console.log('Item ID:', event.itemId);
console.log('Title:', event.title);
console.log('Fields:', event.fields);
console.log('Created by:', event.createdBy);
},
});
// Later, when shutting down
await bridge.stop();Using Helper Functions
import {
createSlackListBridge,
updateSlackListItem,
addSlackListComment,
completeSlackListItem,
} from '@bernierllc/slack-list-bridge';
import { WebClient } from '@slack/web-api';
const client = new WebClient(process.env.SLACK_BOT_TOKEN!);
const bridge = createSlackListBridge({
signingSecret: process.env.SLACK_SIGNING_SECRET!,
botToken: process.env.SLACK_BOT_TOKEN!,
async handler(event) {
try {
// Update item fields
await updateSlackListItem(client, event.listId, event.itemId, {
status: 'processing',
processed_at: new Date().toISOString(),
});
// Add a comment
await addSlackListComment(
client,
event.listId,
event.itemId,
'Processing started!'
);
// Perform your business logic here
await processItem(event);
// Complete the item
await completeSlackListItem(client, event.listId, event.itemId);
// Add completion comment
await addSlackListComment(
client,
event.listId,
event.itemId,
'Processing completed successfully!'
);
} catch (error) {
console.error('Error processing item:', error);
await addSlackListComment(
client,
event.listId,
event.itemId,
`Error: ${error.message}`
);
}
},
});
async function processItem(event: SlackListEvent) {
// Your business logic here
}Custom Port and Path
const bridge = createSlackListBridge({
signingSecret: process.env.SLACK_SIGNING_SECRET!,
botToken: process.env.SLACK_BOT_TOKEN!,
port: 8080,
path: '/webhooks/slack/lists',
async handler(event) {
// Handle event
},
});API Reference
Types
SlackListEvent
interface SlackListEvent {
listId: string;
itemId: string;
title: string;
fields: Record<string, any>;
createdBy: string;
rawPayload: any;
}BridgeConfig
interface BridgeConfig {
port?: number; // Default: 3000
path?: string; // Default: '/slack/list-webhook'
signingSecret: string;
botToken: string;
handler: (event: SlackListEvent) => Promise<void>;
}SlackListBridge
interface SlackListBridge {
stop(): Promise<void>;
}Functions
createSlackListBridge(config: BridgeConfig): SlackListBridge
Creates and starts a Slack list bridge server.
Parameters:
config- Bridge configuration
Returns:
- Bridge instance with
stop()method
Example:
const bridge = createSlackListBridge({
signingSecret: 'your-signing-secret',
botToken: 'xoxb-your-bot-token',
handler: async (event) => {
console.log(event);
},
});updateSlackListItem(client: WebClient, listId: string, itemId: string, updates: Record<string, any>): Promise<any>
Updates fields on a Slack list item.
Parameters:
client- Slack WebClient instancelistId- List IDitemId- Item IDupdates- Fields to update
Returns:
- API response
Example:
await updateSlackListItem(client, 'L123', 'I456', {
status: 'completed',
result: 'success',
});addSlackListComment(client: WebClient, listId: string, itemId: string, comment: string): Promise<any>
Adds a comment to a Slack list item.
Parameters:
client- Slack WebClient instancelistId- List IDitemId- Item IDcomment- Comment text
Returns:
- API response
Example:
await addSlackListComment(client, 'L123', 'I456', 'Processing complete!');completeSlackListItem(client: WebClient, listId: string, itemId: string): Promise<any>
Marks a Slack list item as complete.
Parameters:
client- Slack WebClient instancelistId- List IDitemId- Item ID
Returns:
- API response
Example:
await completeSlackListItem(client, 'L123', 'I456');verifySlackSignature(signingSecret: string, requestSignature: string, timestamp: string, body: string): boolean
Verifies a Slack request signature (used internally by the bridge).
Parameters:
signingSecret- Slack signing secretrequestSignature- Signature fromx-slack-signatureheadertimestamp- Timestamp fromx-slack-request-timestampheaderbody- Raw request body as string
Returns:
trueif signature is valid,falseotherwise
parseSlackListEvent(rawPayload: any): SlackListEvent
Parses and validates a Slack list event payload (used internally by the bridge).
Parameters:
rawPayload- Raw payload from Slack webhook
Returns:
- Parsed and type-safe event object
Throws:
- Error if payload is invalid
Setting Up in Slack
- Create a Slack app at https://api.slack.com/apps
- Enable "Event Subscriptions" and set your webhook URL
- Subscribe to list events
- Install the app to your workspace
- Copy the signing secret and bot token
Security
- All requests are verified using Slack's signature verification
- Timestamps are checked to prevent replay attacks (5-minute window)
- Signatures are compared using timing-safe comparison
Error Handling
The bridge handles errors gracefully:
- Missing signature headers → 401 Unauthorized
- Invalid signature → 401 Unauthorized
- Invalid payload → 500 Internal Server Error
- Handler errors → 500 Internal Server Error (logged)
Testing
npm run test
npm run test:coverageLicense
Copyright (c) 2025 Bernier LLC. This file is licensed to the client under a limited-use license.
