PULSE_AGENT_INGEST_PORT existed in code but was absent from operator-facing docs, so the network-isolation feature (dedicated agent-ingest port serving only /api/agents/*, firewalled from the web UI/management API) was undiscoverable. Adds it to CONFIGURATION.md (env-var row + Split-Port Agent Ingest section with the security model and agent wiring via PULSE_AGENT_CONNECT_URL) and an API Security cross-reference in AGENT_SECURITY.md. Also aligns WEBHOOKS.md multi-tenant licensing wording with MULTI_TENANT.md / API.md (Enterprise license + multi_tenant capability).
7.3 KiB
🔔 Webhooks
Pulse includes built-in templates for popular services and a generic JSON template for custom endpoints.
🚀 Quick Setup
- Go to Alerts → Notification Destinations.
- Click Add Webhook.
- Select service type and paste the URL.
📝 Service URLs
| Service | URL Format |
|---|---|
| Discord | https://discord.com/api/webhooks/{id}/{token} |
| Slack | https://hooks.slack.com/services/... |
| Teams | https://{tenant}.webhook.office.com/webhookb2/{webhook_path} |
| Teams (Adaptive Card) | https://{tenant}.webhook.office.com/webhookb2/{webhook_path} |
| Telegram | https://api.telegram.org/bot{bot_token}/sendMessage?chat_id={chat_id} |
| PagerDuty | https://events.pagerduty.com/v2/enqueue |
| Pushover | https://api.pushover.net/1/messages.json |
| Gotify | https://gotify.example.com/message?token={token} |
| ntfy | https://ntfy.sh/{topic} |
| Generic | https://example.com/webhook |
🎨 Custom Templates
For generic webhooks, use Go templates to format the JSON payload.
Variables (common):
{{.ID}},{{.Level}},{{.Type}}{{.ResourceName}},{{.ResourceID}},{{.ResourceType}},{{.Node}}{{.Message}},{{.Value}},{{.Threshold}},{{.Duration}},{{.Timestamp}}{{.Instance}}(Pulse public URL if configured){{.CustomFields.<name>}}(user-defined fields in the UI){{.Metadata}}(alert metadata map){{.AlertCount}},{{.Alerts}}(grouped alerts){{.Mention}}(platform-specific mention, if configured)
Convenience fields:
{{.ValueFormatted}},{{.ThresholdFormatted}}{{.StartTime}},{{.Acknowledged}},{{.AckTime}},{{.AckUser}}
Template helpers: title, upper, lower, printf, urlquery/urlencode, urlpath/pathescape, jsonString
jsonString is the safe way to embed string values inside a JSON payload — it escapes quotes, backslashes, and control characters without wrapping the value in surrounding quotes, so you can write "text": "{{.Message | jsonString}}" and stay valid JSON even when the message contains " or newlines. Pulse's shipped templates use it extensively; prefer it over manual escaping in custom templates.
Service-specific notes:
- Telegram: include
chat_idin the URL query string. - Telegram templates:
{{.ChatID}}is populated from the URL query string. - PagerDuty: set
routing_keyas a custom field (or header) in the webhook config. - Pushover: add
tokenandusercustom fields (required). Legacyapp_tokenanduser_tokeninputs are migrated automatically.
Example Payload:
{
"text": "Alert: {{.Level}} - {{.Message}}",
"value": {{.Value}}
}
🛡️ Security
- Private IPs: By default, webhooks to private IPs are blocked. Allow them in Settings → System → Network → Webhook Security.
- Headers: Add custom headers (e.g.,
Authorization: Bearer ...) in the webhook config.
🧾 Audit Webhooks (Pro/legacy Pro+/Cloud)
Pro, legacy Pro+, and Cloud support dedicated audit webhooks for security event compliance. Unlike alert notifications, these webhooks deliver the raw, signed JSON payload of every security-relevant action (login, config change, group mapping).
Setup
- Go to Settings → Security → Webhooks.
- Add your endpoint URL (e.g.,
https://siem.corp.local/ingest/pulse).
Security
Audit webhooks are dispatched asynchronously. The payload includes a signature field which can be verified using the per-instance HMAC key stored (encrypted) at .audit-signing.key in the Pulse data directory. There is no PULSE_AUDIT_SIGNING_KEY override.
🏢 Multi-tenant / MSP and PSA integration
In multi-tenant mode (Pulse Cloud, or self-hosted with PULSE_MULTI_TENANT_ENABLED=true and an Enterprise license with the multi_tenant capability) alerts and notification destinations are isolated per organization. Every alert and webhook request resolves an organization and operates only on that org's own alert state and webhook config, so one client's destinations and templates never leak into another's.
The organization for a request is resolved in this order:
X-Pulse-Org-ID: <orgID>header (the way API clients and PSA middleware should target a specific tenant).pulse_org_idsession cookie (browser sessions).- An org-bound API token (a token scoped to a single org needs no header).
- Fallback: the
defaultorg.
Suspended or pending-deletion organizations return 403, and an unknown org ID returns 400.
Wiring tenant alerts into ConnectWise / a PSA
There are two integration models. Most MSPs use the push model so tickets open and close automatically.
Push (recommended): one outbound webhook per tenant org. Create a Generic webhook for each client org and point it at your PSA's inbound endpoint (ConnectWise inbound webhook, an email connector, or a middleware that opens service tickets). Shape the JSON with a custom template so it matches your PSA's expected schema — every template variable listed above is available. Pulse fires on both alert and resolved events ({{.Event}} is "alert" or "resolved"), so the PSA can open a ticket on alert and auto-resolve it on recovery. Add PSA authentication as a custom header (e.g. Authorization: Bearer ...).
Configure it from the UI (Alerts → Notification Destinations → Add Webhook) per org, or programmatically with an org-bound admin token:
POST /api/notifications/webhooks
X-Pulse-Org-ID: acme-corp
Authorization: Bearer <token with settings:write>
Content-Type: application/json
{
"name": "ConnectWise (Acme)",
"url": "https://psa.example.com/inbound/pulse",
"method": "POST",
"service": "generic",
"enabled": true,
"headers": { "Authorization": "Bearer <psa-token>" },
"template": "{\"summary\":\"{{.Level}}: {{.ResourceName}} {{.Message | jsonString}}\",\"event\":\"{{.Event}}\",\"alertId\":\"{{.ID}}\"}"
}
The exact ticket fields differ by PSA (ConnectWise, Autotask, Halo, and others each expect their own inbound shape), so map the template to your platform's contract. The alertId round-trips through {{.ID}}, which lets the PSA correlate the later resolved event to the ticket it opened.
Pull (poll): org-scoped read API. Issue a monitoring:read token bound to each client org and poll that org's alerts. Send X-Pulse-Org-ID (or rely on the org-bound token) so you get only that tenant's data:
GET /api/alerts/active— currently firing alerts for the org.GET /api/alerts/history— historical alerts for the org.
To acknowledge or clear from the PSA side, use a monitoring:write token: POST /api/alerts/acknowledge and POST /api/alerts/clear.
Scope and targeting summary
| Action | Endpoint | Scope |
|---|---|---|
| Create / update / delete per-org webhook | POST / PUT / DELETE /api/notifications/webhooks |
settings:write (admin) |
| List per-org webhooks | GET /api/notifications/webhooks |
settings:read (admin) |
| Read active / historical alerts | GET /api/alerts/active, GET /api/alerts/history |
monitoring:read |
| Acknowledge / clear alerts | POST /api/alerts/acknowledge, POST /api/alerts/clear |
monitoring:write |
Target a tenant with the X-Pulse-Org-ID: <orgID> header or an org-bound API token. See API.md for the full endpoint and token reference.