> ## Documentation Index
> Fetch the complete documentation index at: https://pulse-41cf5b0d.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Receive PulseGuard Alert Webhooks

> Configure a webhook URL to receive real-time POST notifications when monitor status changes, incidents open, or maintenance windows start.

Webhooks let PulseGuard push real-time event notifications to your own infrastructure instead of requiring you to poll the API. When a monitored service goes down, recovers, or triggers an incident, PulseGuard sends an HTTP `POST` request to a URL you configure — with a JSON payload describing exactly what happened. You can use webhooks to build custom alerting, sync incidents to an on-call platform, post to Slack, or trigger automated remediation scripts.

## Set up a webhook

<Steps>
  <Step title="Open Notification Channels">
    In the PulseGuard dashboard, navigate to **Notification Channels**.
  </Step>

  <Step title="Add a Webhook channel">
    Click **Add Channel**, select **Webhook**, and enter the full HTTPS URL that PulseGuard should `POST` to.
  </Step>

  <Step title="Assign the channel to monitors">
    Associate the new channel with one or more monitors from the monitor's **Alert Settings** tab. PulseGuard will send a webhook for every qualifying event on those monitors.
  </Step>

  <Step title="Test the endpoint">
    Use the **Send test event** button to verify that your endpoint receives and processes the payload correctly before relying on it in production.
  </Step>
</Steps>

<Note>
  Always use an HTTPS endpoint. Plain HTTP URLs are rejected to protect the integrity of event data in transit.
</Note>

## Events that trigger webhooks

PulseGuard sends a webhook for each of the following events:

| Event                 | Description                                              |
| --------------------- | -------------------------------------------------------- |
| `monitor.down`        | A monitor has failed and crossed the alert threshold     |
| `monitor.up`          | A previously failing monitor has recovered               |
| `monitor.maintenance` | A scheduled maintenance window has started for a monitor |
| `incident.created`    | A new incident has been opened                           |
| `incident.resolved`   | An open incident has been resolved                       |

## Webhook payload

Every webhook delivers a JSON body with a consistent structure regardless of the event type.

```json theme={null}
{
  "event": "monitor.down",
  "monitorId": "clx1234abc",
  "monitorName": "Production API",
  "status": "DOWN",
  "previousStatus": "UP",
  "latency": 0,
  "errorReason": "CONNECTION_REFUSED",
  "region": "us-east-1",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "runbookUrl": "https://docs.example.com/runbooks/api"
}
```

<Expandable title="Payload fields">
  <ResponseField name="event" type="string" required>
    The type of event that fired. One of `monitor.down`, `monitor.up`, `monitor.maintenance`, `incident.created`, `incident.resolved`.
  </ResponseField>

  <ResponseField name="monitorId" type="string" required>
    Unique identifier of the affected monitor.
  </ResponseField>

  <ResponseField name="monitorName" type="string" required>
    Human-readable name of the monitor.
  </ResponseField>

  <ResponseField name="status" type="string" required>
    The monitor's new status after the event: `UP`, `DOWN`, or `MAINTENANCE`.
  </ResponseField>

  <ResponseField name="previousStatus" type="string" required>
    The monitor's status immediately before the event.
  </ResponseField>

  <ResponseField name="latency" type="integer" required>
    Round-trip time in milliseconds for the check that triggered the event. `0` for connection-level failures.
  </ResponseField>

  <ResponseField name="errorReason" type="string | null" required>
    Machine-readable failure reason for `DOWN` events. Possible values: `TIMEOUT`, `DNS_ERROR`, `CONNECTION_REFUSED`, `HTTP_<code>` (e.g. `HTTP_503`), `UNKNOWN_ERROR`. `null` for `UP` events.
  </ResponseField>

  <ResponseField name="region" type="string" required>
    Region code that produced the check result (e.g. `us-east-1`).
  </ResponseField>

  <ResponseField name="timestamp" type="string" required>
    ISO 8601 timestamp of when the event occurred.
  </ResponseField>

  <ResponseField name="runbookUrl" type="string | null">
    Runbook URL configured on the monitor, if any. `null` if none is set.
  </ResponseField>
</Expandable>

## Handle webhooks in your application

Your endpoint must respond with a `2xx` status code within **5 seconds**. If it does not, PulseGuard marks the delivery as failed. Below is a minimal Express.js handler that receives and processes PulseGuard webhooks.

```javascript webhook-handler.js theme={null}
import express from 'express';
const app = express();
app.use(express.json());

app.post('/webhook/pulseguard', (req, res) => {
  // Respond immediately to acknowledge receipt
  res.sendStatus(200);

  const { event, monitorName, status, errorReason, runbookUrl } = req.body;

  if (event === 'monitor.down') {
    console.error(`ALERT: ${monitorName} is ${status} — ${errorReason}`);
    if (runbookUrl) console.log(`Runbook: ${runbookUrl}`);
    // Trigger your paging, Slack, or incident-management logic here
  }

  if (event === 'monitor.up') {
    console.log(`RECOVERY: ${monitorName} is back UP`);
    // Auto-resolve incidents, send all-clear notifications, etc.
  }
});

app.listen(3000);
```

<Tip>
  Send `res.sendStatus(200)` before running any slow logic (database writes, external API calls). This keeps your response time well under the 5-second timeout and prevents duplicate deliveries.
</Tip>

## Verify webhook authenticity

PulseGuard includes a `User-Agent` header on every webhook request:

```
User-Agent: PulseGuard/1.0
```

Check for this header as a basic guard against spoofed requests arriving at your endpoint.

```javascript theme={null}
app.post('/webhook/pulseguard', (req, res) => {
  if (req.headers['user-agent'] !== 'PulseGuard/1.0') {
    return res.sendStatus(403);
  }
  res.sendStatus(200);
  // ... handle event
});
```

## Best practices

<CardGroup cols={2}>
  <Card title="Respond fast" icon="bolt">
    Always reply with `2xx` within 5 seconds. Offload slow work to a background queue or async function after sending the response.
  </Card>

  <Card title="Use HTTPS only" icon="lock">
    Configure only HTTPS webhook URLs. Plain HTTP endpoints are not accepted, as they expose event data to interception.
  </Card>

  <Card title="Handle duplicates" icon="copy">
    Webhook deliveries are at-least-once. Use the combination of `monitorId` + `timestamp` as an idempotency key when writing events to your database.
  </Card>

  <Card title="Check User-Agent" icon="shield-halved">
    Validate the `User-Agent: PulseGuard/1.0` header to filter out requests that did not originate from PulseGuard.
  </Card>
</CardGroup>

<Warning>
  If your endpoint consistently fails to respond within 5 seconds or returns non-`2xx` status codes, PulseGuard may disable the channel to protect delivery queues. Check your dashboard's **Notification Channels** page for delivery error details.
</Warning>
