Skip to main content
SitePulse can deliver issue lifecycle alerts to URLs you control. This guide covers webhook-style channels: the generic Webhook channel, Slack webhook, Microsoft Teams, connected Slack, and connected Discord.
Email and in-app notifications use separate delivery paths. See Notifications for the full channel overview.

Configuration fields

FieldApplies toDescription
ChannelAllwebhook (your own HTTP endpoint), slack (incoming webhook URL), slack_app (OAuth connection; SitePulse stores the webhook URL after you connect), discord (OAuth connection; SitePulse stores the webhook URL after you connect), or microsoft_teams (workflow incoming webhook URL)
DestinationURL channelsPublic http:// or https:// webhook URL, max 2,048 characters
HTTP methodwebhook onlypost (default) or get. Slack, Slack app, and Teams always use POST.
EventsAllopen (Issue detected — new issues and reopens, plus escalation follow-ups) and/or resolve (Issue resolved — automatic monitoring confirms recovery)
Tool scopeAllAll tools, or a subset of enabled check tools
EnabledAllToggle delivery without deleting the configuration
Configurations can be tested with a sample payload before and after saving (see Test payload). A configuration delivers an alert only when all of the following are true:
  1. The configuration is enabled
  2. The issue transition matches a subscribed event (open, resolve, or escalate via an Issue detected subscription)
  3. The issue’s check tool is in scope (all tools, or listed in the configuration)
  4. The team is not in quiet time
  5. The destination URL passes SitePulse’s outbound safety checks
Acknowledging or refreshing an issue does not send webhook alerts. Open (including reopen), resolve, and escalate transitions can trigger delivery when subscribed.

Delivery behavior

Webhook alerts are sent asynchronously after the issue transition commits to the database.
BehaviorDetail
TimingQueued job; not synchronous with the check that opened the issue
Quiet timeSuppresses all outbound issue alerts (webhooks, email, in-app)
Connect timeout5 seconds
Total timeout10 seconds
RetriesUp to 3 attempts at 200ms, 500ms, and 1000ms intervals
RedirectsDisabled
Response handlingNon-2xx responses are logged; monitoring and issue state are unaffected
FailuresExceptions are logged and swallowed — they do not roll back the issue
SitePulse does not retry failed webhooks beyond the short HTTP retry window. Build idempotent handlers and do not rely on webhooks as the only record of an incident.

Request format

POST (recommended)

SitePulse sends Content-Type: application/json with Accept: application/json. Generic webhook — raw issue lifecycle JSON (see Production payload below). Slack webhook / Slack app — Slack Block Kit message JSON with text and blocks fields. Discord — Discord webhook JSON with content and embeds fields. Microsoft Teams — Office 365 Connector MessageCard JSON.

GET (generic webhook only)

SitePulse sends the production payload as query string parameters on the configured URL. Nested objects are encoded using Laravel’s query serialization (e.g. issue[summary]=..., team[id]=...). Use GET only when your receiver cannot accept POST bodies. POST is recommended for nested payloads.

Headers

All HTTP webhook deliveries include channel-specific headers where applicable:
HeaderTest deliveryIssue deliveryValue
X-SitePulse-Eventnotification.testissue.opened, issue.recovered, or issue.escalatedEvent name
X-SitePulse-Deliverytestissue-lifecycleDelivery type
The X-SitePulse-Event header mirrors the event field in the JSON body (for POST) or query payload (for GET).

Event mapping

UI labelInternal transitionevent valueWhen it fires
Issue detectedopenissue.openedNew issue opens
Issue detectedopen (from reopen)issue.openedPreviously resolved issue fails again
Issue resolvedresolveissue.recoveredAutomatic monitoring confirms recovery
Issue escalationescalateissue.escalatedFollow-up reminder while issue stays open or acknowledged
The transition field in the payload reflects the internal transition (open, resolve, or escalate), not reopen.

Production payload

Generic Webhook channel deliveries use this JSON shape on POST:
{
  "event": "issue.opened",
  "transition": "open",
  "occurred_at": "2026-05-25T12:00:00+00:00",
  "team": {
    "id": 1,
    "name": "Example Team"
  },
  "issue": {
    "id": 123,
    "type": "status",
    "status": "open",
    "severity": "critical",
    "fingerprint": "status:down",
    "summary": "Example is down",
    "link": "https://app.sitepulse.dev/dashboard/issues?status=active&site=..."
  },
  "site": {
    "id": 10,
    "uuid": "9d4e2c8f-1234-5678-9abc-def012345678",
    "name": "Example",
    "url": "https://example.com",
    "domain": "example.com"
  }
}

Field reference

FieldTypeDescription
eventstringissue.opened, issue.recovered, or issue.escalated
transitionstringopen, resolve, or escalate
occurred_atstringISO 8601 timestamp when the alert was generated
team.idintegerTeam id
team.namestringTeam display name
issue.idintegerInternal issue id
issue.typestringCheck tool: status, ssl, dns, broken-links, performance
issue.statusstringIssue status at time of delivery (open, acknowledged, or resolved)
issue.severitystringcritical, warning, or info
issue.fingerprintstringStable deduplication key (e.g. status:down, ssl:expiring)
issue.summarystringHuman-readable issue title
issue.linkstringURL to the filtered Issues page in SitePulse
site.idintegerInternal site id
site.uuidstringSite UUID (used in API paths)
site.namestringSite display name
site.urlstringMonitored URL
site.domainstringHostname derived from the URL
Read issue history programmatically with the Issues API (issues:read scope). Webhook issue.id is the internal integer id, not the UUID exposed by the API.

Test payload

The Test button sends a different payload from production issue alerts. Use it to verify connectivity, not to validate your issue handler.
{
  "event": "notification.test",
  "sent_at": "2026-05-25T12:00:00+00:00",
  "team": {
    "id": 1,
    "name": "Example Team"
  },
  "configuration": {
    "id": 42,
    "channel": "webhook",
    "destination": "https://hooks.example.com/sitepulse"
  },
  "message": "This is a test webhook from SitePulse."
}
Test deliveries set X-SitePulse-Delivery: test and X-SitePulse-Event: notification.test. Slack and Microsoft Teams test messages use channel-specific formatting (Slack blocks or Teams MessageCard) with the same test metadata. You can test unsaved modal values from the configuration dialog before creating or updating a configuration.

Channel-specific formatting

Slack (app and webhook)

Production Slack messages are POSTed as JSON with:
  • text — short summary line
  • blocks — formatted sections for summary, site, tool, severity, status, and a View issue button linking to issue.link
Titles:
  • Issue detected: SitePulse detected an issue: {site name}
  • Issue resolved: SitePulse recovered: {site name}

Discord

Production Discord messages are POSTed as JSON with:
  • content — short summary line
  • embeds — formatted fields for site, tool, severity, status, and a link to the issue
Discord uses red embeds for detected issues and green embeds for recoveries.

Microsoft Teams

Production Teams messages are POSTed as MessageCard JSON with:
  • themeColor — red (DC2626) for detected issues, green (16A34A) for recoveries
  • title, text, and sections with site/tool/severity/status facts
  • potentialAction with a View issue link

Destination URL requirements

Webhook URLs must be:
  • Valid http:// or https:// URLs
  • Publicly reachable from SitePulse workers
  • Free of embedded credentials (user:pass@host is rejected)
SitePulse blocks destinations that resolve to private, loopback, or link-local addresses. This includes hostnames like localhost, *.local, and hosts whose DNS resolves to non-public IPs.
Internal network URLs (private RFC 1918 ranges, metadata endpoints, etc.) are rejected by design.

Example receiver

Minimal POST handler (Node.js / Express):
app.post('/sitepulse/hooks', express.json(), (req, res) => {
  const event = req.get('X-SitePulse-Event');

  if (event === 'notification.test') {
    console.log('Test webhook received');
    return res.sendStatus(200);
  }

  if (event === 'issue.opened') {
    console.log(`Issue opened: ${req.body.issue.summary}`);
    // Page on-call, create ticket, etc.
  }

  if (event === 'issue.recovered') {
    console.log(`Issue recovered: ${req.body.issue.summary}`);
    // Close ticket, notify channel, etc.
  }

  res.sendStatus(200);
});
Return any 2xx status promptly. SitePulse treats other responses as delivery failures.

Integration recipes

Event type reference

Use this table when building idempotent handlers for the generic Webhook channel (POST JSON):
X-SitePulse-Eventevent (body)transitionSuggested action
notification.testnotification.testVerify connectivity only
issue.openedissue.openedopenPage on-call, open ticket, post to chat
issue.escalatedissue.escalatedescalateSend a reminder for an active issue
issue.recoveredissue.recoveredresolveResolve ticket, notify recovery
Slack, Slack webhook, Discord, and Microsoft Teams channels receive formatted payloads, not the raw issue JSON above. Use the generic Webhook channel for custom integrations.

PagerDuty (Events API v2)

Route SitePulse alerts to PagerDuty using a generic webhook and a small translation layer.
  1. Create a Webhook configuration: method POST, events Issue detected and Issue resolved, destination set to your middleware URL.
  2. Map SitePulse events to PagerDuty trigger and resolve actions using X-SitePulse-Event or event in the body.
  3. Use issue.fingerprint combined with site.uuid as the PagerDuty dedup_key so reopen and recovery events update the same incident.
Example middleware (forwards to PagerDuty Events API v2):
app.post('/sitepulse/pagerduty', express.json(), async (req, res) => {
  const event = req.get('X-SitePulse-Event');
  const { issue, site } = req.body;

  if (event === 'notification.test') {
    return res.sendStatus(200);
  }

  const dedupKey = `${site.uuid}:${issue.fingerprint}`;
  const summary = issue.summary || `SitePulse alert for ${site.name}`;

  const payload = {
    routing_key: process.env.PAGERDUTY_ROUTING_KEY,
    event_action: event === 'issue.recovered' ? 'resolve' : 'trigger',
    dedup_key: dedupKey,
    payload: {
      summary,
      severity: issue.severity === 'critical' ? 'critical' : 'warning',
      source: site.domain,
      custom_details: issue,
    },
  };

  await fetch('https://events.pagerduty.com/v2/enqueue', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });

  res.sendStatus(200);
});
Store PAGERDUTY_ROUTING_KEY in your middleware environment, not in SitePulse.