npm package: @pulse-pm/bridge
Description: PulsePM Bridge - Proxy for on-premise GitHub Enterprise and Jira Data Center webhooks
Latest version: 0.5.1

# 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 Cloud
```

**Webhook 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 Cloud
```

**Webhook 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)

```bash
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:latest
```

### NPM

```bash
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-bridge
```

### Docker Compose

```yaml
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-stopped
```

---

## Configuration

### 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

```bash
curl http://localhost:3001/health
```

Response:
```json
{
  "status": "ok",
  "version": "0.1.0",
  "uptime": 3600,
  "startedAt": "2026-02-16T10:00:00.000Z",
  "lastEventAt": "2026-02-16T11:30:00.000Z"
}
```

### Statistics

```bash
curl http://localhost:3001/stats
```

Response:
```json
{
  "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_at` timestamp 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:**
```json
{
  "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

```bash
# Using the dev script
./scripts/bridge-start.sh

# Or manually
cd apps/bridge
pnpm dev
```

### 2. Simulate Enterprise Webhooks

```bash
# 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-prem
```

### 3. Check Bridge Logs

```bash
tail -f .dev-pids/bridge.log
```

---

## Security

### 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:**
```bash
echo $PULSE_API_URL
echo $ORG_ID
# Don't echo API key in production!
```

**Check logs:**
```bash
docker logs pulse-bridge
# Or
tail -f .dev-pids/bridge.log
```

### Events not forwarding

**Check bridge health:**
```bash
curl http://localhost:3001/health
```

**Check bridge stats:**
```bash
curl http://localhost:3001/stats
```

**Verify API key:**
- Go to `/dashboard/org/integrations/bridge` in 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_SECRET` matches 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

```yaml
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: 3001
```

### Systemd Service

```ini
[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.target
```

---

## License

MIT

---

## Support

- Documentation: https://docs.pulsepm.io
- Issues: https://github.com/pulse-pm/pulse-pm/issues
- Email: support@pulsepm.io