npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-bridge

Usage

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 instance
  • listId - List ID
  • itemId - Item ID
  • updates - 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 instance
  • listId - List ID
  • itemId - Item ID
  • comment - 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 instance
  • listId - List ID
  • itemId - 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 secret
  • requestSignature - Signature from x-slack-signature header
  • timestamp - Timestamp from x-slack-request-timestamp header
  • body - Raw request body as string

Returns:

  • true if signature is valid, false otherwise

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

  1. Create a Slack app at https://api.slack.com/apps
  2. Enable "Event Subscriptions" and set your webhook URL
  3. Subscribe to list events
  4. Install the app to your workspace
  5. 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:coverage

License

Copyright (c) 2025 Bernier LLC. This file is licensed to the client under a limited-use license.