Files
pulse/docs/WEBHOOKS.md
T
rcourtman f8ca38587c docs: document split-port agent ingest for operators (MSP isolation)
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).
2026-05-29 09:54:11 +01:00

7.3 KiB

🔔 Webhooks

Pulse includes built-in templates for popular services and a generic JSON template for custom endpoints.

🚀 Quick Setup

  1. Go to Alerts → Notification Destinations.
  2. Click Add Webhook.
  3. 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_id in the URL query string.
  • Telegram templates: {{.ChatID}} is populated from the URL query string.
  • PagerDuty: set routing_key as a custom field (or header) in the webhook config.
  • Pushover: add token and user custom fields (required). Legacy app_token and user_token inputs 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

  1. Go to Settings → Security → Webhooks.
  2. 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:

  1. X-Pulse-Org-ID: <orgID> header (the way API clients and PSA middleware should target a specific tenant).
  2. pulse_org_id session cookie (browser sessions).
  3. An org-bound API token (a token scoped to a single org needs no header).
  4. Fallback: the default org.

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.