Enterprise-Grade Webhook Router & ConnectWise Bridge
HookWise is a highly performant, general-purpose webhook router designed to bridge various monitoring sources (Uptime Kuma, Zabbix, Grafana, Datadog) to ConnectWise Manage tickets. Featuring intelligent duplicate detection, asynchronous processing, and local AI-driven analysis.
- Architecture & Flow
- Advanced Features
- API Reference
- AI In-Depth
- Extensive Configuration
- Deep-Dive Usage
- Configuration Recipes
- Troubleshooting & FAQ
- Security & Compliance
- Development & Contributing
HookWise uses a distributed architecture to ensure reliability and low-latency webhook ingestion.
graph TD
Client[Monitoring Source] -->|HTTPS Webhook| Proxy[Flask / Gevent Proxy]
Proxy -->|Queue Task| Redis[(Redis Broker)]
Redis -->|Process| Worker[Celery Worker]
Worker -->|Analyze| AI[Ollama / phi3]
Worker -->|PSA API| CW[ConnectWise Manage]
Worker -->|Logs| DB[(PostgreSQL)]
Proxy -->|Live Feed| GUI[Web GUI / Socket.io]
- Ingestion: Proxy receives payload, validates source IP and HMAC signature.
- Queuing: Request is assigned a
request_idand pushed to Redis. - Processing: Celery worker pulls the task.
- Resolution: JSONPath mappings and Regex routing rules are applied.
- Deduplication: PSA is queried for existing open tickets with the same summary.
- Action: Ticket is Created, Updated, or Closed in ConnectWise.
- AI Insights: For new tickets, Ollama generates an automated RCA note.
flowchart TD
A[Start Process] --> B{Existing Open Ticket?}
B -- Yes --> C{Status in Payload?}
C -- "Close Value" --> D[Close Ticket]
C -- Other --> E[Add Internal Note]
B -- No --> F{Status in Payload?}
F -- "Open Value" --> G[Create New Ticket]
F -- Other --> H[Skip/Log Only]
G --> I[Analyze with AI]
I --> J[Add RCA Note]
flowchart TD
A[Incoming Webhook] --> B{Global Maintenance?}
B -- Yes --> C[Log as Skipped]
B -- No --> D{Window Matches?}
D -- "Daily Schedule" --> C
D -- "Weekly Day" --> C
D -- No Match --> E[Process Normally]
sequenceDiagram
participant S as Monitoring Source
participant P as Flask Proxy
participant R as Redis Broker
participant W as Celery Worker
participant A as Ollama (phi3)
participant C as ConnectWise API
participant D as PostgreSQL DB
S->>P: POST /webhook/<id> (Bearer Auth)
P->>P: Validate Source & HMAC
P->>R: Push Task ID
P-->>S: 202 Accepted (Request ID)
R->>W: Fetch Task
W->>C: Check for Duplicate Entry
alt Exists
W->>C: Update Ticket / Add Note
else New
W->>C: Create Ticket
W->>A: Analyze Payload
A-->>W: Root Cause Note
W->>C: Add Internal RCA Note
end
W->>D: Persist Final Status & Logs
- Regex Rule Engine: Route
CRITICALalerts to the "Emergency" board andWARNalerts to "Tiling" automatically. - Smart Maintenance: Define recurring maintenance windows (Daily, Weekly, Once) with support for overnight schedules (e.g., 22:00 to 04:00) using UTC-normalized logic.
- Company Mapping: Supports
#CW<ID>in titles or dynamic lookups from payload fields. - Webhook Timeout Alerts (Heartbeat): Automatically trigger a ticket if an endpoint hasn't received data within a configured threshold (e.g., "No data for 24h"). Alerts repeat at the same hourly interval if the endpoint remains stale, adding a note to the existing ticket or creating a new one if it was closed. The alert state resets as soon as the next webhook arrives.
HookWise can generate automated troubleshoot guides using local LLMs. It analyzes the raw payload and adds an internal note to the ticket with:
- Potential root causes.
- Suggested troubleshooting steps.
- Technical summary of the alert.
Managing the Model:
By default, HookWise uses phi3. To pull the latest version or update the model manually:
docker exec -it hookwise-llm ollama pull phi3- Live Activity Hub: Real-time Socket.io feed of all incoming webhooks.
- Secure Management: Integrated endpoint deletion with confirmation prompts and CSRF protection.
- Audit Trail: Every configuration change is logged with the user and timestamp.
- Prometheus Metrics: Native Export for scrapers like Grafana.
All GUI/Admin endpoints require Session Auth or Basic Auth (if configured). Webhook endpoints require Bearer tokens.
POST /webhook/<endpoint_id>- Auth:
Authorization: Bearer <token> - Returns:
202 Acceptedwithrequest_id.
- Auth:
GET /api/stats: Returns daily performance data.POST /history/replay/<log_id>: Re-processes a historic webhook.GET /api/cw/boards: Cached proxy to ConnectWise boards.GET /health/services: Real-time health check for Redis, DB, and Celery.POST /admin/maintenance: Toggle global maintenance mode.
HMAC (Hash-based Message Authentication Code) provides a way to verify both the integrity and the authenticity of a webhook. It ensures that the payload hasn't been tampered with and truly originated from your monitoring tool.
- Shared Secret: You and HookWise share a secret key (configured per endpoint).
- Signing: Your monitor tool calculates a SHA256 hash of the raw request body using that secret.
- Transmission: The tool sends this hash in the
X-HookWise-Signatureheader. - Verification: HookWise recalculates the hash and compares it. If they don't match, the request is rejected.
If your monitoring tool supports custom headers and signing scripts, use the following logic:
1. Calculate the Signature (Python Example):
import hmac
import hashlib
secret = "your_hmac_secret_from_gui"
payload = '{"status": "0", "msg": "Critical Alert"}' # Raw body string
signature = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
print(f"Header Value: {signature}")2. Send the Request:
- Header:
X-HookWise-Signature: <calculated_signature> - Content-Type:
application/json
Important
Always sign the raw, unformatted body. If your tool beautifies the JSON (adds spaces/newlines) after signing, the verification will fail.
HookWise leverages local LLMs via Ollama to provide instant RCA (Root Cause Analysis). This means no data ever leaves your network.
By default, HookWise uses phi3:latest. You can swap this for llama3, mistral, or any other model supported by Ollama:
- Pull the model:
docker exec -it hookwise-llm ollama pull llama3 - Update Configuration: Set the
AI_MODELenvironment variable tollama3. - Restart Worker: The Celery worker will now use the new model for all analysis.
The analysis is guided by a global system prompt that tells the AI to be concise and focused on remediation. You can customize the RCA Instructions per endpoint in the Web GUI, allowing different alerts to receive different styles of analysis (e.g., "Developer-focused" vs "Support-focused").
The LLM_MAX_TOKENS environment variable controls how many tokens Ollama is allowed to generate per RCA response. If your notes appear cut off mid-sentence, this value is too low.
| Value | Expected Output | RAM Impact | Best For |
|---|---|---|---|
100 |
1β2 sentences (often truncated) | ~2β3 GB | Testing only β not recommended |
256 |
Short paragraph, may truncate complex alerts | ~2β3 GB | Low-RAM environments (β€ 4 GB) |
512 (default) |
Full RCA with 3β5 bullet points | ~3β4 GB | Most deployments |
1024 |
Detailed multi-section analysis | ~4β6 GB | High-volume or complex MSP environments |
2048 |
Very long, comprehensive notes | ~6β8 GB | Dedicated LLM host with 8+ GB RAM |
Tip
Set LLM_MAX_TOKENS=512 in your docker-compose.yml or .env. Larger values increase response time linearly β expect ~5β10s per 512 tokens on a 4-core host.
Note
Token β word. Roughly 1 token β 0.75 words. 512 tokens β ~380 words β enough for a complete, structured RCA note.
| Variable | Usage |
|---|---|
CW_TICKET_PREFIX |
Prefix for all summaries (Default: Alert:). |
CW_SERVICE_BOARD |
Primary board if not overridden. |
CW_STATUS_NEW |
Initial status for new tickets. |
CW_STATUS_CLOSED |
Status used when an UP alert is received. |
VIABILITY_TTL |
Seconds a ticket is cached as "open" before re-checking ConnectWise (Default: 300). |
| Variable | Usage |
|---|---|
ENCRYPTION_KEY |
32-byte Fernet key. DO NOT LOSE. |
GUI_TRUSTED_IPS |
CIDR list (e.g., 10.0.0.0/24, 192.168.1.5). |
LOG_RETENTION_DAYS |
Auto-cleanup limit for webhook_log table. |
FORCE_HTTPS |
Redirects all traffic to TLS. |
LLM_MAX_TOKENS |
Max tokens for LLM RCA responses (Default: 512). Increase if output is truncated. |
LLM_TIMEOUT |
Seconds to wait for the LLM to respond (Default: 180). Increase on slow/CPU-only hosts. |
| Destination | Path Example | Result |
|---|---|---|
| Summary | $.monitor.name |
Extracts Uptime Kuma monitor name. |
| Description | $.msg |
Extracts the alert body. |
| Company | $.tags.client_id |
Maps dynamic client IDs. |
HookWise supports combining multiple JSONPath variables in a single field. Simply space-separate the paths. Empty or null variables in the payload will be automatically ignored. Any segment not starting with $ is treated as literal text.
Note
The field will only be overridden if at least one JSONPath resolves to a non-empty value. Literal-only results are ignored to prevent accidental data loss.
- Example Mapping:
"summary": "$.TaskInfo.Tenant $.TaskInfo.Name" - Payload 1:
{"TaskInfo": {"Tenant": "Acme", "Name": "SRV01"}}-> Result:Acme SRV01 - Payload 2:
{"TaskInfo": {"Name": "SRV01"}}-> Result:SRV01 - Payload 3:
"summary": "Prefix $.SomePath"where$.SomePathis missing -> Result: No Override (Default monitor name is used). - Payload 4:
"summary": "Prefix $.SomePath"where$.SomePathexists -> Result:Prefix Value
Use these in your "Ticket Description Template":
{{ monitor_name }}: The alert source name.{{ msg }}: The alert message.{{ request_id }}: Internal tracking ID.{$..field}: Any valid JSONPath (e.g.,{$..heartbeat.status}).
/: Focus Search bar.Esc: Close any open modal.Drag & Drop: Reorder endpoint priority on the dashboard.
Perfect for basic UP/DOWN monitoring.
- Trigger Field:
$.heartbeat.status - Open Value:
0 - Close Value:
1 - JSON Mapping:
{ "summary": "$.monitor.name", "description": "$.heartbeat.msg", "customer_id": "$.monitor.tags.CW_ID" }
For tools that send text-based statuses like "CRITICAL" or "OK".
- Trigger Field:
$.status_text - Open Value:
CRITICAL, WARNING - Close Value:
OK, RESOLVED - Ticket Prefix:
Infrastructure Alert:
Route alerts to different boards based on the hostname.
- Routing Rules:
[ { "path": "$.monitor.hostname", "regex": ".*-DB-.*", "overrides": { "board": "Database Team", "priority": "High" } }, { "path": "$.monitor.hostname", "regex": ".*-FE-.*", "overrides": { "board": "Frontend Team" } } ]
Great for detailed system health and event severity.
- Trigger Field:
$.event.status - Open Value:
PROBLEM - Close Value:
OK, RESOLVED - JSON Mapping:
{ "summary": "$.event.name", "severity": "$.event.severity", "description": "Trigger: {$.trigger.description}\nHost: {$.host.name}" }
Handle firing and resolved alerts from Grafana dashboards.
- Trigger Field:
$.status - Open Value:
firing - Close Value:
resolved - JSON Mapping:
{ "summary": "$.alerts[0].annotations.summary", "description": "$.alerts[0].annotations.description" }
HookWise provides several ways to automatically map alerts to the correct ConnectWise Client without creating separate endpoints for every customer.
If your monitor name contains #CW followed by a ConnectWise Company Identifier, HookWise will automatically route the ticket to that company.
- Example Monitor Name:
Firewall Down #CW-AcmeCorp - Result: Ticket created for company
AcmeCorp.
Map a specific field in the webhook payload directly to the ConnectWise company ID.
- Mapping:
"customer_id": "$.tags.client_id"
Use Routing Rules to map specific hostnames or message patterns to different companies.
- Rule:
{"path": "$.host", "regex": "PRD-CL1-.*", "overrides": {"customer_id": "CLIENT_A"}}
HookWise provides a centralized mapping table called TenantMap (found in the navbar). This allows you to map common client identifiers (like domains or company IDs) once and apply them globally across all your endpoints.
- Centralized Link: Map
example.com->EXAMPLEjust once. - Auto-Scanning: HookWise intelligently scans incoming payloads for fields like
Tenant,tenantId, and$.TaskInfo.Tenant. - Per-Endpoint Toggle: You can enable or disable TenantMap lookups for each specific endpoint in its configuration form.
Tip
Use TenantMap for high-volume client identification to avoid repeating the same mapping rules in dozens of different endpoint configurations.
Q: Why are tickets not closing automatically?
- Verify that your
Close Valuein the endpoint config matches the payload exactly (e.g.,1vsUP). - Check if the ticket summary has been manually changed in ConnectWise.
Q: "Redis connection refused" in logs?
- Ensure the
rediscontainer is running and theREDIS_PASSWORDmatches in both theredisandhookwiseservices.
Q: AI RCA is too slow?
- LLM inference is CPU-heavy. Ensure the
hookwise-llmcontainer has at least 4 cores and 8GB RAM assigned. - Consider switching to a smaller model (e.g.,
phi3:3.8binstead of larger variants).
Q: Getting "400 Bad Request" when creating tickets?
- This usually means ConnectWise rejected the payload due to a missing or invalid field.
- Check the History: Go to the "History" tab in the GUI. I've updated the logging to show the exact error message from ConnectWise in the "Error Message" column.
- Common causes: Invalid
board,priority, orstatusname that doesn't exist on the target board.
Q: Metrics at /metrics are missing some counters?
- If you don't see
hookwise_webhooks_totalor other custom metrics, ensure your Celery worker and Web proxy can both reach the same Redis instance. - HookWise uses Redis to aggregate metrics across process boundaries; if Redis is down or partitioned, counters will restart at zero or appear empty.
Q: HMAC verification fails on every request?
- Ensure your monitoring tool is sending the payload as raw JSON.
- If your tool adds extra whitespace or re-orders JSON keys after signing, the signature won't match.
- Try testing with the
X-HookWise-Signatureheader disabled first to verify the basic connectivity.
- Data Privacy: Webhook payloads are masked (
********) in audit logs if they contain sensitive keys liketokenorpassword. - Encryption: Bearer tokens and HMAC secrets are encrypted using AES-128 via the Fernet protocol.
- Secure Identifiers: Uses high-entropy, 64-character URL-safe tokens for endpoint IDs to prevent brute-force discovery.
- Air-Gap Support: All assets (Bootstrap, Socket.io, Prism.js) are bundled locally. No external CDNs are used.
We use ruff for code quality:
ruff check .
ruff format .When changing models.py:
flask db migrate -m "Description"
flask db upgradeMIT License - Copyright (c) 2026 HookWise Team.
