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

pk-alert-hook

v0.1.1

Published

Lightweight runtime error alerting via Google Chat webhooks — instant notifications when your services break.

Readme

pk-alert-hook

Lightweight runtime error alerting via Google Chat webhooks — instant notifications when your services break. No dashboard, no paid alert providers, just alerts where your team already works.

Google Chat only. More providers (Slack, Discord) may come later. For now, this library supports Google Chat webhooks exclusively.

Install

npm install pk-alert-hook

Requires Node.js 18+ (uses native fetch).

Google Chat Webhook Setup

  1. Open Google Chat → open or create a Space
  2. Click the space name at the top → Apps & integrationsManage webhooks
  3. Click Add another, name it pk-alert-hook, click Save
  4. Copy the webhook URL (looks like https://chat.googleapis.com/v1/spaces/.../messages?key=...&token=...)
  5. Add to your .env:
PK_ALERT_GOOGLE_CHAT_WEBHOOK_URL=https://chat.googleapis.com/v1/spaces/YOUR_SPACE/messages?key=YOUR_KEY&token=YOUR_TOKEN

Security: This URL contains auth tokens. Never commit it to git. Make sure .env is in your .gitignore.

If you don't see "Manage webhooks", your Workspace admin needs to enable it: Admin Console → Apps → Google Workspace → Google Chat → Manage webhooks: ON

Quick Start

import { AlertHook } from 'pk-alert-hook';

// Initialize once at app startup
AlertHook.init({
  webhookUrl: process.env.PK_ALERT_GOOGLE_CHAT_WEBHOOK_URL,
  environment: process.env.NODE_ENV,
  allowedEnvs: ['production', 'staging'],
  appName: 'my-api',
});

// Use in any catch block
try {
  await processOrder(orderId);
} catch (err) {
  AlertHook.capture(err, { orderId, userId });
  throw err;
}

Quick Test

Test locally before deploying:

# Clone and install
git clone <repo-url> && cd pk-alert-hook
npm install

# Set up your webhook
cp examples/.env.example examples/.env
# Edit examples/.env with your real webhook URL

# Send 3 test alerts (error, warning, info)
npx tsx examples/basic.ts

Check your Google Chat space — you should see 3 cards within seconds.

Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | webhookUrl | string | required | Google Chat incoming webhook URL | | environment | string | required | Current environment (production, staging, etc.) | | allowedEnvs | string[] | required | Only send alerts in these environments | | appName | string | required | Shown in alert header | | version | string | — | App version shown in footer | | maxStackLength | number | 500 | Max characters for stack trace | | timezone | string | 'UTC' | IANA timezone string (e.g. 'Asia/Bangkok') | | rateLimitWindowMs | number | 300000 | Dedup window in ms (default 5min) | | rateLimitEnabled | boolean | true | Enable/disable rate limiting | | silent | boolean | false | Suppress internal console warnings | | showPreviewText | boolean | true | Show notification preview text above card (useful for desktop/mobile notifications, adds a text line above the card in chat) |

API

AlertHook.init(config)

Initialize with config. Validates with Zod — throws on invalid config. Call once at startup.

AlertHook.capture(error, context?)

Capture an error. Fire-and-forget — never throws, never blocks your app.

AlertHook.capture(error, { userId: 'usr_123', route: '/api/orders' });

AlertHook.alert(message, context?, severity?)

Send a manual alert. Defaults to WARNING severity.

import { Severity } from 'pk-alert-hook';

AlertHook.alert('Disk usage at 90%', { disk: '/dev/sda1' });
AlertHook.alert('Deploy complete', {}, Severity.INFO);

AlertHook.setGlobalContext(context)

Set persistent context merged into every alert.

AlertHook.setGlobalContext({ region: 'us-east-1', service: 'payment-api' });

AlertHook.clearGlobalContext()

Clear all global context.

AlertHook.flush()

Await all pending alert sends. Call during graceful shutdown.

process.on('SIGTERM', async () => {
  await AlertHook.flush();
  process.exit(0);
});

AlertHook.destroy()

Tear down the instance. Stops rate limiter, clears state.

Rate Limiting

Two layers of protection to prevent flooding your Google Chat space.

1. Dedup Rate Limiter (per error)

Groups identical errors within a time window so the same crash doesn't spam your chat.

  • Creates a fingerprint from error message + first stack frame
  • First occurrence → sent immediately
  • Same error within 5 minutes (default) → suppressed, counter increments
  • After window expires → next occurrence sent with "Occurred N times" badge
  • Different errors are always independent
// Change the dedup window
AlertHook.init({ ...config, rateLimitWindowMs: 600_000 }); // 10 minutes

// Disable dedup entirely (every error sends a message)
AlertHook.init({ ...config, rateLimitEnabled: false });

2. Global Send Throttle (per minute)

Google Chat enforces 60 messages/minute per space. pk-alert-hook caps at 50/minute (with 10 buffer) regardless of dedup settings.

  • If 200 unique errors fire in 1 second → first 50 send, rest dropped
  • Dropped alerts log a console.warn (unless silent: true)
  • Sends resume automatically after the rolling minute window passes
  • This limit is always active and cannot be disabled — it protects your webhook from being blocked by Google

Safety Guarantees

  • Never breaks your app. Every public method is wrapped in try-catch. If pk-alert-hook fails internally, it logs a warning and moves on.
  • Fire-and-forget. capture() and alert() return void. Webhook calls happen in the background.
  • No memory leaks. Promises self-clean after resolving. Rate limiter is capped at 1000 entries with automatic cleanup.
  • Only init() throws — intentionally — so bad config fails fast at startup.

Framework Examples

Express

app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  AlertHook.capture(err, { method: req.method, path: req.path });
  res.status(500).json({ error: 'Internal server error' });
});

NestJS

@Catch()
export class AlertHookFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const req = host.switchToHttp().getRequest();
    AlertHook.capture(exception, { method: req.method, path: req.url });
  }
}

Next.js API Route

export async function POST(request: Request) {
  try {
    // ... handle request
  } catch (err) {
    AlertHook.capture(err, { route: 'POST /api/endpoint' });
    return Response.json({ error: 'Internal server error' }, { status: 500 });
  }
}

Contributing

This is a personal project and a portfolio showcase. Contributions are not accepted — see CONTRIBUTING.md for details. You're welcome to use, fork, and learn from the code.

License

MIT