# Webhook IP Whitelisting Guide

## Overview

This guide explains how to identify Autotask's webhook source IPs and configure IP whitelisting to secure your webhook endpoint.

---

## Why IP Whitelisting?

**Benefits:**
- ✅ Only allow webhooks from Autotask's servers
- ✅ Block unauthorized webhook attempts
- ✅ Additional security layer beyond HTTPS
- ✅ Prevent abuse and DDoS attacks

**When to Use:**
- Production environments
- High-security requirements
- After identifying Autotask's IP ranges

---

## Step 1: Identify Autotask IPs

### **Method 1: View Webhook Logs (Recommended)**

After receiving a few webhooks from Autotask, check the logged IPs:

**Via API:**
```bash
curl https://your-domain.com/api/webhooks/ips?hours=168
```

Response:
```json
{
  "success": true,
  "ips": [
    {
      "ip": "52.1.2.3",
      "userAgent": "Autotask-Webhook/1.0",
      "requestCount": 150,
      "successfulCount": 148,
      "failedCount": 2,
      "firstSeen": "2026-01-20T10:00:00Z",
      "lastSeen": "2026-01-24T15:00:00Z",
      "entityTypes": ["Tickets", "Companies", "Tasks"]
    },
    {
      "ip": "52.5.6.7",
      "userAgent": "Autotask-Webhook/1.0",
      "requestCount": 75,
      "successfulCount": 75,
      "failedCount": 0,
      "firstSeen": "2026-01-22T08:00:00Z",
      "lastSeen": "2026-01-24T14:30:00Z",
      "entityTypes": ["TimeEntries", "Projects"]
    }
  ],
  "count": 2,
  "period": "168 hours"
}
```

**Via Database:**
```sql
-- Get unique IPs with request counts
SELECT 
  source_ip,
  user_agent,
  COUNT(*) as request_count,
  MIN(received_at) as first_seen,
  MAX(received_at) as last_seen,
  COUNT(*) FILTER (WHERE status = 'processed') as successful,
  COUNT(*) FILTER (WHERE status = 'failed') as failed
FROM webhook_logs
WHERE received_at >= NOW() - INTERVAL '7 days'
  AND source_ip IS NOT NULL
GROUP BY source_ip, user_agent
ORDER BY request_count DESC;
```

**Identify Autotask IPs:**
- Look for IPs with `Autotask-Webhook` user agent
- High request counts (if you have active webhooks)
- Multiple entity types
- Consistent activity pattern

### **Method 2: Contact Autotask Support**

Request official IP ranges from Autotask:
- Open support ticket
- Ask for "Webhook source IP ranges"
- They may provide CIDR blocks (e.g., `52.1.0.0/16`)

### **Method 3: Check Autotask Documentation**

Some Autotask zones publish their IP ranges:
- Check Autotask developer documentation
- Look for "Webhook IP ranges" or "API IP addresses"
- May vary by zone (ww1, ww5, ww15, etc.)

---

## Step 2: Configure IP Whitelist

### **Option A: Nginx**

Edit your nginx configuration:

```nginx
server {
    listen 443 ssl http2;
    server_name webhooks.yourdomain.com;
    
    # ... SSL configuration ...
    
    location = /api/webhooks/autotask {
        # IP Whitelist - Autotask webhook IPs
        allow 52.1.2.3;      # Autotask IP 1
        allow 52.5.6.7;      # Autotask IP 2
        allow 52.10.0.0/16;  # Autotask IP range (CIDR)
        deny all;            # Block everything else
        
        # Rate limiting
        limit_req zone=webhook_limit burst=20 nodelay;
        
        # Proxy to Pulse app
        proxy_pass http://pulse_app;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
```

**Test and reload:**
```bash
sudo nginx -t
sudo systemctl reload nginx
```

### **Option B: Pangolin**

Update your Pangolin configuration:

```yaml
# /etc/pangolin/config.yml

name: pulse-webhooks

ingress:
  - hostname: webhooks.yourdomain.com
    path: /api/webhooks/autotask
    service: http://localhost:3100

security:
  # IP Whitelist
  ipWhitelist:
    - "52.1.2.3"        # Autotask IP 1
    - "52.5.6.7"        # Autotask IP 2
    - "52.10.0.0/16"    # Autotask IP range
  
  # Rate limiting
  rateLimit:
    enabled: true
    requestsPerMinute: 100
    burstSize: 20
```

**Restart tunnel:**
```bash
sudo systemctl restart pangolin-tunnel
```

### **Option C: Cloudflare Tunnel**

Use Cloudflare Access rules:

1. **Go to Cloudflare Dashboard** → Zero Trust → Access → Applications
2. **Create Application:**
   - Name: `Pulse Webhooks`
   - Domain: `webhooks.yourdomain.com`
   - Path: `/api/webhooks/autotask`
3. **Add Policy:**
   - Name: `Autotask IPs Only`
   - Action: `Allow`
   - Rule: `IP ranges`
   - Values: `52.1.2.3`, `52.5.6.7`, `52.10.0.0/16`
4. **Save**

### **Option D: Firewall (UFW)**

If using UFW on the server:

```bash
# Allow specific IPs to port 443
sudo ufw allow from 52.1.2.3 to any port 443
sudo ufw allow from 52.5.6.7 to any port 443
sudo ufw allow from 52.10.0.0/16 to any port 443

# Deny all other traffic to port 443 (if not already blocked)
# Be careful with this - ensure you have other access methods!
# sudo ufw deny 443

# Check rules
sudo ufw status numbered
```

**Warning:** Be careful with firewall rules. Ensure you don't lock yourself out!

---

## Step 3: Test IP Whitelist

### **1. Test from Allowed IP (Autotask)**

Trigger a webhook from Autotask:
- Create a test ticket or company
- Check webhook logs to verify it was received

```bash
# Check recent webhooks
curl https://your-domain.com/api/webhooks/logs?limit=5
```

### **2. Test from Blocked IP**

From a different IP (your computer):

```bash
curl -X POST https://webhooks.yourdomain.com/api/webhooks/autotask \
  -H "Content-Type: application/json" \
  -d '{"eventId":"test","eventType":"create","entityType":"Tickets","entityId":1}'
```

**Expected result:** 
- **Nginx:** `403 Forbidden`
- **Pangolin/Cloudflare:** Connection refused or 403
- **Firewall:** Connection timeout

### **3. Verify Logs**

Check that only Autotask IPs are getting through:

```sql
-- Recent webhook IPs
SELECT source_ip, COUNT(*) as count
FROM webhook_logs
WHERE received_at >= NOW() - INTERVAL '1 hour'
GROUP BY source_ip;
```

Should only show Autotask IPs.

---

## Monitoring IP Changes

### **Set Up Alerts for New IPs**

Create a monitoring query to detect new source IPs:

```sql
-- Find IPs seen in last 24 hours that weren't seen before
SELECT DISTINCT wl.source_ip, wl.user_agent, MIN(wl.received_at) as first_seen
FROM webhook_logs wl
WHERE wl.received_at >= NOW() - INTERVAL '24 hours'
  AND wl.source_ip NOT IN (
    SELECT DISTINCT source_ip 
    FROM webhook_logs 
    WHERE received_at < NOW() - INTERVAL '24 hours'
      AND received_at >= NOW() - INTERVAL '30 days'
  )
GROUP BY wl.source_ip, wl.user_agent;
```

### **Regular IP Audit**

Run weekly to check for IP changes:

```bash
# Get IPs from last 7 days
curl https://your-domain.com/api/webhooks/ips?hours=168 > webhook-ips-$(date +%Y%m%d).json

# Compare with previous week
diff webhook-ips-20260117.json webhook-ips-20260124.json
```

If new IPs appear:
1. Verify they're legitimate Autotask IPs
2. Check user agent matches `Autotask-Webhook`
3. Update whitelist configuration
4. Reload/restart proxy or tunnel

---

## Troubleshooting

### **Webhooks Stopped Working After Whitelisting**

**Check 1: Verify Autotask IP**
```sql
-- Check recent failed webhooks
SELECT source_ip, user_agent, error_message, received_at
FROM webhook_logs
WHERE status = 'failed'
  AND received_at >= NOW() - INTERVAL '1 hour'
ORDER BY received_at DESC;
```

If you see legitimate Autotask IPs being blocked, add them to whitelist.

**Check 2: Proxy Logs**

**Nginx:**
```bash
tail -f /var/log/nginx/pulse-webhooks-error.log | grep 403
```

**Pangolin:**
```bash
sudo journalctl -u pangolin-tunnel -f | grep denied
```

**Check 3: Test Without Whitelist**

Temporarily disable IP whitelist to confirm that's the issue:

**Nginx:** Comment out `allow`/`deny` lines and reload
**Pangolin:** Remove `ipWhitelist` section and restart

If webhooks work without whitelist, the issue is IP configuration.

### **Autotask Changed IPs**

Autotask may change their webhook source IPs:

1. **Check webhook logs** for new IPs
2. **Verify** they're legitimate (user agent, request pattern)
3. **Update whitelist** with new IPs
4. **Keep old IPs** for a few days (in case of rollback)
5. **Remove old IPs** after confirming they're no longer used

### **Getting Blocked Yourself**

If you accidentally block legitimate traffic:

**Quick Fix (Nginx):**
```bash
# Edit config to remove IP whitelist
sudo nano /etc/nginx/sites-available/pulse-webhooks

# Comment out the deny rules
# deny all;

# Reload
sudo systemctl reload nginx
```

**Quick Fix (Pangolin):**
```bash
# Edit config
sudo nano /etc/pangolin/config.yml

# Remove or comment ipWhitelist section

# Restart
sudo systemctl restart pangolin-tunnel
```

---

## Best Practices

### **1. Use CIDR Blocks When Possible**

Instead of individual IPs:
```nginx
# Better
allow 52.10.0.0/16;

# Less flexible
allow 52.10.1.1;
allow 52.10.1.2;
allow 52.10.1.3;
```

### **2. Document Your Whitelist**

Keep a record of:
- Which IPs are whitelisted
- When they were added
- Source of information (Autotask support, logs, etc.)
- Last verified date

Example:
```yaml
# IP Whitelist Documentation
# Last updated: 2026-01-24

ipWhitelist:
  - "52.1.2.3"        # Added: 2026-01-20, Source: Webhook logs
  - "52.5.6.7"        # Added: 2026-01-22, Source: Webhook logs  
  - "52.10.0.0/16"    # Added: 2026-01-24, Source: Autotask support ticket #12345
```

### **3. Monitor Regularly**

- Weekly: Check for new IPs in logs
- Monthly: Audit whitelist against active IPs
- After Autotask updates: Verify webhooks still work

### **4. Combine with Other Security**

IP whitelisting is one layer. Also use:
- ✅ HTTPS/TLS encryption
- ✅ Rate limiting
- ✅ Path-based access control
- ✅ Request validation
- ✅ Monitoring and alerting

### **5. Have a Rollback Plan**

Keep configuration backups:
```bash
# Backup before changes
sudo cp /etc/nginx/sites-available/pulse-webhooks \
       /etc/nginx/sites-available/pulse-webhooks.backup-$(date +%Y%m%d)

# Or for Pangolin
sudo cp /etc/pangolin/config.yml \
       /etc/pangolin/config.yml.backup-$(date +%Y%m%d)
```

---

## API Reference

### **GET /api/webhooks/ips**

Get unique source IPs from webhook logs.

**Parameters:**
- `hours` (optional): Number of hours to look back (default: 168 = 7 days)
- `entityType` (optional): Filter by entity type

**Example:**
```bash
# Last 7 days
curl https://your-domain.com/api/webhooks/ips?hours=168

# Last 24 hours, tickets only
curl https://your-domain.com/api/webhooks/ips?hours=24&entityType=Tickets
```

**Response:**
```json
{
  "success": true,
  "ips": [
    {
      "ip": "52.1.2.3",
      "userAgent": "Autotask-Webhook/1.0",
      "requestCount": 150,
      "successfulCount": 148,
      "failedCount": 2,
      "firstSeen": "2026-01-20T10:00:00Z",
      "lastSeen": "2026-01-24T15:00:00Z",
      "entityTypes": ["Tickets", "Companies"]
    }
  ],
  "count": 1,
  "period": "168 hours"
}
```

---

## Database Queries

### **Get All Unique IPs**
```sql
SELECT DISTINCT source_ip, user_agent
FROM webhook_logs
WHERE source_ip IS NOT NULL
ORDER BY source_ip;
```

### **Get IP Activity Summary**
```sql
SELECT 
  source_ip,
  COUNT(*) as total_requests,
  COUNT(DISTINCT entity_type) as entity_types_count,
  MIN(received_at) as first_seen,
  MAX(received_at) as last_seen,
  ROUND(AVG(processing_time_ms), 2) as avg_processing_ms
FROM webhook_logs
WHERE source_ip IS NOT NULL
GROUP BY source_ip
ORDER BY total_requests DESC;
```

### **Detect Suspicious IPs**
```sql
-- IPs with high failure rates
SELECT 
  source_ip,
  user_agent,
  COUNT(*) as total,
  COUNT(*) FILTER (WHERE status = 'failed') as failed,
  ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'failed') / COUNT(*), 2) as failure_rate
FROM webhook_logs
WHERE source_ip IS NOT NULL
  AND received_at >= NOW() - INTERVAL '24 hours'
GROUP BY source_ip, user_agent
HAVING COUNT(*) FILTER (WHERE status = 'failed') > 5
ORDER BY failure_rate DESC;
```

---

## Summary

**Steps to Enable IP Whitelisting:**

1. ✅ **Run migration** to add IP logging columns
2. ✅ **Deploy updated code** with IP capture
3. ✅ **Receive webhooks** from Autotask (let it run for a day)
4. ✅ **Identify Autotask IPs** via `/api/webhooks/ips` endpoint
5. ✅ **Configure whitelist** in your proxy/tunnel
6. ✅ **Test** that webhooks still work
7. ✅ **Monitor** for new IPs regularly

**Result:**
- Only Autotask's IPs can send webhooks
- Unauthorized requests are blocked
- Additional security layer for your webhook endpoint
- Logged IP data for audit and troubleshooting
