Event Hub is a small webhook gateway for payment and event notifications.
It lets you:
- create tokenized webhook endpoints
- receive webhook POST payloads on token URLs
- fan out incoming payloads to WebSocket clients for the same token
- monitor webhook events in a browser admin UI
- protect admin endpoints with OIDC login (Zitadel)
This project is useful when you want one stable webhook URL per integration and real-time delivery to downstream apps.
Typical flow:
- Create token in admin UI
- Use generated webhook URL in external service (for example LNbits LNURLp)
- Connect your consumer to generated WebSocket URL
- Receive events in real time
- OIDC login with Authorization Code + PKCE
- Token management UI at /admin
- Webhook monitor with event history and live stream
- Token persistence across restarts (file-based)
- Token-specific WebSocket channels
- GET /
- Landing page with links to admin, health endpoint, and source repository
- GET /health
- JSON health check
- GET /auth/login
- Starts OIDC login
- GET /auth/callback
- OIDC callback endpoint
- GET /admin
- Protected admin UI
- GET /admin/tokens
- Protected token list API
- POST /admin/tokens
- Protected token create API
- DELETE /admin/tokens/:token
- Protected token delete API
- GET /hook/:token
- Returns endpoint status/help text
- POST /hook/:token
- Webhook ingestion endpoint
- WS /ws/:token
- WebSocket stream for a token
- Copy .env.example to .env and set values.
- Build and run:
docker compose up -d --build- Open:
- BASE_URL/auth/login
- after login you should be redirected to BASE_URL/admin
Requirements:
- Node.js 20+
Steps:
npm installCreate .env (copy from .env.example), then start:
node src/server.jsSee .env.example for the full list.
Important values:
- APP_PORT: application listen port
- BASE_URL: public HTTPS URL used for redirects and generated webhook/ws URLs
- GITHUB_URL: repository URL shown on landing page
- SESSION_SECRET: express-session signing secret
- TOKENS_FILE: file path for persisted tokens
- ZITADEL_ISSUER: Zitadel issuer URL
- ZITADEL_CLIENT_ID: Zitadel app client ID
- ZITADEL_CLIENT_SECRET: optional for confidential client, empty for public PKCE client
Tokens are persisted to TOKENS_FILE. In Docker Compose, ./data is mounted to /app/data so tokens survive container restarts.
Use a dedicated application in Zitadel for Event Hub.
- Create application
- Type: Web
- Response Type: Code
- Grant Type: Authorization Code
- Authentication Method:
- None for Public + PKCE
- client secret method for Confidential client
- Redirect URI
- Add exactly:
- PKCE
- Enable PKCE (S256)
- Copy credentials to .env
- ZITADEL_ISSUER=https://your-zitadel-domain
- ZITADEL_CLIENT_ID=
- ZITADEL_CLIENT_SECRET=<optional; empty for public client>
- Ensure BASE_URL matches your public domain exactly
- BASE_URL=https://webhook.your-domain
- Restart app after env changes
- Login at /auth/login
- Open /admin
- Create a token
- Copy webhook and WebSocket URLs
- Configure external service to POST to webhook URL
- Connect your consumer to WebSocket URL
- In admin, click Watch to inspect incoming events
TOKEN="your-token"
curl -i -X POST "https://webhook.your-domain/hook/${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"test":"ping","source":"curl"}'Expected result: HTTP 200 OK
Open:
This is a GET availability check only. Real webhook delivery must use POST.
- Cannot GET /hook/
- Use POST for actual webhook delivery.
- OIDC callback returns authentication failed
- Verify redirect URI, BASE_URL, client type, and PKCE settings.
- Login works but no websocket processing
- Verify your consumer is connected to WS /ws/ and parses incoming JSON.
- curl works but external service does not
- Check sender logs and payload format; verify sender is using exact webhook URL.
- Replace SESSION_SECRET in production.
- Use HTTPS in front of the app.
- For production scale, replace in-memory session store with Redis or another shared store.

