@pulse-pm/bridge
v0.5.1
Published
PulsePM Bridge - Proxy for on-premise GitHub Enterprise and Jira Data Center webhooks
Downloads
101
Maintainers
Readme
PulsePM Bridge
The PulsePM Bridge is a lightweight proxy for forwarding webhooks from on-premise GitHub Enterprise and Jira Data Center instances to PulsePM cloud.
When Do You Need the Bridge?
✅ Use the Bridge if:
- Your GitHub Enterprise or Jira Data Center instance is air-gapped (no internet access)
- Your organization has strict firewall rules that block outbound webhooks
- You need all traffic to go through an approved internal proxy
❌ Skip the Bridge if:
- Your GitHub Enterprise or Jira Data Center can reach the public internet
- In this case, configure webhooks to point directly to PulsePM cloud (see Direct Mode below)
Architecture Modes
Mode 1: Direct (Recommended if internet-accessible)
GitHub Enterprise / Jira Data Center
↓ (webhook with bridge auth header)
PulsePM CloudWebhook URLs:
GitHub Enterprise:
https://app.pulsepm.io/api/webhooks/github?org=<ORG_ID>&connection=<CONNECTION_NAME>
Jira Data Center:
https://app.pulsepm.io/api/webhooks/jira?org=<ORG_ID>&connection=<CONNECTION_NAME>Headers:
x-bridge-auth: Bearer <BRIDGE_API_KEY>
Generate your bridge API key at: /dashboard/org/integrations/bridge
Mode 2: Bridge (Air-gapped environments)
GitHub Enterprise / Jira Data Center
↓ (webhook to local bridge)
Bridge (running in your network)
↓ (forwarded with bridge auth)
PulsePM CloudWebhook URLs (point to your local bridge):
GitHub Enterprise:
http://<BRIDGE_HOST>:3001/webhook/github?connection=<CONNECTION_NAME>
Jira Data Center:
http://<BRIDGE_HOST>:3001/webhook/jira?connection=<CONNECTION_NAME>Installation
Docker (Recommended)
docker run -d \
-p 3001:3001 \
-e PULSE_API_URL=https://app.pulsepm.io \
-e PULSE_API_KEY=<your-bridge-api-key> \
-e ORG_ID=<your-org-id> \
--name pulse-bridge \
--restart unless-stopped \
ghcr.io/pulse-pm/bridge:latestNPM
npm install -g @pulse-pm/bridge
# Run
PULSE_API_URL=https://app.pulsepm.io \
PULSE_API_KEY=<your-bridge-api-key> \
ORG_ID=<your-org-id> \
pulse-bridgeDocker Compose
version: '3.8'
services:
pulse-bridge:
image: ghcr.io/pulse-pm/bridge:latest
ports:
- "3001:3001"
environment:
- PULSE_API_URL=https://app.pulsepm.io
- PULSE_API_KEY=pbk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- ORG_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
- GITHUB_SECRET=optional-webhook-secret
- JIRA_SECRET=optional-webhook-secret
- PORT=3001
restart: unless-stoppedConfiguration
Required Environment Variables
PULSE_API_URL- PulsePM cloud URL (e.g.,https://app.pulsepm.io)PULSE_API_KEY- Bridge API key (generate at/dashboard/org/integrations/bridge)ORG_ID- Your organization ID (found in dashboard)
Optional Environment Variables
PORT- Bridge port (default:3001)GITHUB_SECRET- GitHub webhook secret for local validation (optional)JIRA_SECRET- Jira webhook secret for local validation (optional)LOG_LEVEL- Log level:info,warn,error,debug(default:info)
Monitoring
Health Check
curl http://localhost:3001/healthResponse:
{
"status": "ok",
"version": "0.1.0",
"uptime": 3600,
"startedAt": "2026-02-16T10:00:00.000Z",
"lastEventAt": "2026-02-16T11:30:00.000Z"
}Statistics
curl http://localhost:3001/statsResponse:
{
"version": "0.1.0",
"uptime": 3600,
"startedAt": "2026-02-16T10:00:00.000Z",
"lastEventAt": "2026-02-16T11:30:00.000Z",
"events": {
"total": 150,
"github": 100,
"jira": 50
},
"errors": {
"total": 2,
"github": 1,
"jira": 1
}
}Health Pings
The bridge automatically sends health pings to PulsePM cloud every 60 seconds. These pings:
- Update the
last_seen_attimestamp in the dashboard - Allow the UI to show whether the bridge is active
- Include version and uptime information
Endpoint: POST {PULSE_API_URL}/api/bridge/ping
Authentication: Uses the same x-bridge-auth: Bearer <PULSE_API_KEY> header
Payload:
{
"orgId": "your-org-id",
"version": "0.1.0",
"uptime": 3600
}The dashboard will show:
- Active - If ping received in last 5 minutes (shows time since last ping)
- Configured - If bridge exists but no ping received yet
- Not configured - If no bridge setup for the org
Testing Locally
1. Start the Bridge
# Using the dev script
./scripts/bridge-start.sh
# Or manually
cd apps/bridge
pnpm dev2. Simulate Enterprise Webhooks
# Send events through the bridge
pnpm dlx tsx tools/webhook-simulator/simulate.ts sprint --count 10 --on-prem
# Simulate a realistic workflow through bridge
pnpm dlx tsx tools/webhook-simulator/simulate.ts scenario --jira 3 --commits 6 --on-prem3. Check Bridge Logs
tail -f .dev-pids/bridge.logSecurity
Webhook Signature Validation
The bridge can optionally validate webhook signatures from GitHub Enterprise and Jira Data Center before forwarding to PulsePM cloud.
Set GITHUB_SECRET and/or JIRA_SECRET environment variables to enable local validation.
API Key Security
- Bridge API keys use the format:
pbk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - Keys are hashed with bcrypt before storage
- Keys are validated on every forwarded request
- Rotate keys regularly via the dashboard
Network Security
- Deploy the bridge in your internal network
- Use firewall rules to restrict access to the bridge
- Only the bridge needs outbound access to PulsePM cloud
- GitHub Enterprise/Jira Data Center only need access to the bridge (internal)
Troubleshooting
Bridge won't start
Check environment variables:
echo $PULSE_API_URL
echo $ORG_ID
# Don't echo API key in production!Check logs:
docker logs pulse-bridge
# Or
tail -f .dev-pids/bridge.logEvents not forwarding
Check bridge health:
curl http://localhost:3001/healthCheck bridge stats:
curl http://localhost:3001/statsVerify API key:
- Go to
/dashboard/org/integrations/bridgein PulsePM - Check that the bridge is marked as "Active" and "Last Seen" is recent
- Regenerate the API key if needed
Webhook signature validation failing
- Ensure
GITHUB_SECRET/JIRA_SECRETmatches what's configured in GitHub/Jira - Check that the secret is properly URL-encoded if it contains special characters
- Try disabling local validation (remove secret env vars) to isolate the issue
Production Deployment
Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: pulse-bridge
spec:
replicas: 1
selector:
matchLabels:
app: pulse-bridge
template:
metadata:
labels:
app: pulse-bridge
spec:
containers:
- name: bridge
image: ghcr.io/pulse-pm/bridge:latest
ports:
- containerPort: 3001
env:
- name: PULSE_API_URL
value: "https://app.pulsepm.io"
- name: PULSE_API_KEY
valueFrom:
secretKeyRef:
name: pulse-bridge-secrets
key: api-key
- name: ORG_ID
value: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
livenessProbe:
httpGet:
path: /health
port: 3001
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 3001
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: pulse-bridge
spec:
selector:
app: pulse-bridge
ports:
- port: 3001
targetPort: 3001Systemd Service
[Unit]
Description=PulsePM Bridge
After=network.target
[Service]
Type=simple
User=pulse
Environment="PULSE_API_URL=https://app.pulsepm.io"
Environment="PULSE_API_KEY=pbk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Environment="ORG_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Environment="PORT=3001"
ExecStart=/usr/bin/node /opt/pulse-bridge/dist/index.js
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=multi-user.targetLicense
MIT
Support
- Documentation: https://docs.pulsepm.io
- Issues: https://github.com/pulse-pm/pulse-pm/issues
- Email: [email protected]
