Skip to main content

Integrations

Webhook Integration

Configure custom webhook integrations to connect Pingara with any external system. Understand payload formats, event types, authentication, and testing strategies.

5 min readUpdated September 28, 2026

webhookapicustomintegration

Webhooks let you connect Pingara with any system that can receive HTTP POST requests, such as custom dashboards, ticketing systems, ChatOps bots, automation platforms, or your own backend services.

How Webhooks Work

When an alert condition is met, Pingara sends an HTTP POST request to your configured endpoint with a JSON payload containing all incident details.

Pingara detects incident
        ↓
POST https://your-endpoint.com/pingara-webhook
Content-Type: application/json
        ↓
Your system receives and processes the event

Setting Up a Webhook

Step 1: Prepare Your Endpoint

Create an HTTP endpoint that:

  • Accepts POST requests
  • Parses JSON request bodies
  • Returns a 2xx status code on success
  • Is publicly accessible from the internet

Example (Node.js/Express):

app.post('/pingara-webhook', (req, res) => {
  const event = req.body;
  console.log('Pingara event:', event.event, event.monitor.name);
  
  // Process the event...
  
  res.status(200).json({ received: true });
});

Step 2: Add the Webhook in Pingara

  1. Go to Settings → Alert Policies → [Your Policy]
  2. Click Add Channel
  3. Select Webhook
  4. Enter your endpoint URL
  5. Click Save

Step 3: Test the Webhook

Click Send Test to send a sample event to your endpoint. Verify:

  • Your endpoint receives the request
  • The JSON payload is parsed correctly
  • Your endpoint returns 200 OK

Payload Format

Standard Payload Structure

Every incident-related webhook event follows this structure. monitor carries only id, name and url, with no status or region, so infer the status from the event name. incident only carries the fields below; there is no status, startedAt, resolvedAt, or performance metrics block, and there is no top-level organization object:

{
  "event": "incident.created",
  "timestamp": "2024-01-15T14:32:05.000Z",
  "monitor": {
    "id": "m1aa2b3c4d5e6f",
    "name": "API Production",
    "url": "https://api.example.com/health"
  },
  "incident": {
    "id": "i9zz8y7x6w5v4u",
    "errorType": "timeout",
    "errorMessage": "Connection timeout after 30000ms",
    "affectedRegions": ["us-east-1", "eu-west-1"]
  }
}

incident.duration (a formatted string like "12 mins, 4 secs", not a number of seconds) is only present on incident.resolved and incident.escalated events. incident.escalationCount is only present on incident.escalated. Both are omitted entirely, not sent as null, when they don't apply.

Event Types

EventTriggerIncident field present
incident.createdAn incident enters the down state, either opened fresh (2 consecutive failures, quorum-confirmed) or as an already-open degraded incident promoted to down. Same event either way; there is no separate "promoted" eventYes
incident.degradedNew incident opened for a degraded transition, from sustained slow responses or a keyword mismatch on a monitor whose keyword-failure severity is DegradedYes
incident.resolvedIncident resolved automatically (2 consecutive successes, quorum-confirmed)Yes, includes duration
incident.escalatedAn unacknowledged incident reaches its next escalation step. Escalation is keyed on the incident still being open and unacknowledged, not on its severity, so a degraded incident escalates on the same schedule as a down oneYes, includes duration and escalationCount
ssl.expiringA certificate crosses a configured expiry-warning thresholdNo, an ssl block instead
domain.expiringA monitored domain's registration crosses a configured expiry-warning thresholdNo, a domain block instead
monitor.pausedMonitor was pausedNo
monitor.resumedMonitor was resumedNo
monitor.monitoring_interruptedEvery probe region configured for this monitor is in a confirmed blackout, so Pingara cannot check it. This says nothing about your endpointYes, an incident-shaped block with errorType: "monitoring_interrupted", but there is no incident record behind it
monitor.monitoring_resumedThe probe regions came back and checks are running againYes, same shape, errorType: "monitoring_interrupted"

There is no incident.updated event. A manual status change (Investigating → Identified → Monitoring) doesn't dispatch a webhook. See Understanding Incidents for what manual status changes do and don't notify.

Event-Specific Payloads

incident.resolved:

{
  "event": "incident.resolved",
  "timestamp": "2024-01-15T14:44:00.000Z",
  "monitor": {
    "id": "m1aa2b3c4d5e6f",
    "name": "API Production",
    "url": "https://api.example.com/health"
  },
  "incident": {
    "id": "i9zz8y7x6w5v4u",
    "errorType": "timeout",
    "errorMessage": "Connection timeout after 30000ms",
    "affectedRegions": ["us-east-1", "eu-west-1"],
    "duration": "12 mins, 4 secs"
  }
}

ssl.expiring:

{
  "event": "ssl.expiring",
  "timestamp": "2024-01-15T00:00:00.000Z",
  "monitor": {
    "id": "m1aa2b3c4d5e6f",
    "name": "API Production",
    "url": "https://api.example.com"
  },
  "ssl": {
    "daysUntilExpiry": 14,
    "issue": "certificate_expiring",
    "recommendation": "Renew the certificate before expiry and verify automatic renewal is active."
  }
}

There is no expiresAt or issuer field in this payload. If you need the certificate's absolute expiry timestamp, read sslExpiresAt for the monitor from the Developer API instead of trying to derive it from daysUntilExpiry. A webhook subscription's ssl.expiring event, a different feature from this alert channel, does carry data.ssl.expiresAt directly, as epoch milliseconds.

domain.expiring:

{
  "event": "domain.expiring",
  "timestamp": "2024-01-15T00:00:00.000Z",
  "monitor": {
    "id": "m1aa2b3c4d5e6f",
    "name": "API Production",
    "url": "https://api.example.com"
  },
  "domain": {
    "name": "example.com",
    "daysUntilExpiry": 14,
    "issue": "registration_expiring",
    "recommendation": "Renew the domain at the registrar, and check that auto-renew is on and the payment method on file is current."
  }
}

An already-lapsed domain sends "issue": "registration_expired" with a negative daysUntilExpiry instead. That's channel-only wording. A webhook subscription's domain.expiring event signals the same lapse with data.domain.milestone: 0.

Authentication

Pingara does not sign or authenticate outgoing webhook requests today. There is no shared-secret header, no bearer token, and no way to configure custom headers on a webhook channel. The only headers a delivery carries are Content-Type: application/json and User-Agent: Pingara-Webhook/1.0, and the latter is trivially spoofable, so don't rely on it as proof a request came from Pingara.

The practical mitigation available today is to keep your endpoint URL private and hard to guess, using a long random path segment rather than a predictable one like /pingara-webhook. Anyone who has the URL can post to it, so treat it like a credential: don't log it, commit it, or share it outside your team.

If verified webhook signing matters for your integration, email support@pingara.io. It isn't available yet, but we'd like to know it's blocking you.

Retry Behavior

If your endpoint fails in a way that might clear on its own, Pingara retries the webhook up to 3 times after the initial attempt (4 attempts total):

AttemptDelay after previous attempt
Initial attempt-
1st retry1 minute
2nd retry5 minutes
3rd retry (final)15 minutes

Not every failure is retried. Pingara classifies the response first, and only retries what could plausibly succeed later:

ResponseRetried?
5xx (server error)Yes
408 (request timeout), 429 (rate limited)Yes
Connection error, timeout, no responseYes
3xx (redirect)No, a redirected webhook delivery is refused outright, never followed
Any other 4xx (400, 401, 403, 404, …)No, a bad URL, payload or credential won't fix itself in 21 minutes

A non-retried failure is marked failed immediately, after a single attempt, so if your endpoint answers 404 while you're deploying, you get one delivery attempt and no more. After the final retry of a retried failure fails, the notification is likewise marked as failed in the notification history.

Handling Retries

Your endpoint should be idempotent. Processing the same event twice should be safe. Use the incident.id and event fields to deduplicate:

const processedEvents = new Set();

app.post('/pingara-webhook', (req, res) => {
  const eventKey = `${req.body.incident?.id}-${req.body.event}`;
  
  if (processedEvents.has(eventKey)) {
    return res.status(200).json({ status: 'already_processed' });
  }
  
  processedEvents.add(eventKey);
  // Process the event...
  
  res.status(200).json({ received: true });
});

Common Integration Patterns

Create a Ticket on Incident

app.post('/pingara-webhook', async (req, res) => {
  if (req.body.event === 'incident.created') {
    await ticketSystem.create({
      title: `[Pingara] ${req.body.monitor.name} is down`,
      description: req.body.incident.errorMessage,
      priority: 'high',
      tags: ['monitoring', 'auto-created'],
    });
  }
  res.status(200).json({ received: true });
});

Post to a Custom Dashboard

app.post('/pingara-webhook', async (req, res) => {
  await dashboard.addEvent({
    source: 'pingara',
    type: req.body.event,
    monitor: req.body.monitor.name,
    timestamp: req.body.timestamp,
    errorType: req.body.incident?.errorType,
    duration: req.body.incident?.duration,
  });
  res.status(200).json({ received: true });
});

Trigger an Automated Remediation

app.post('/pingara-webhook', async (req, res) => {
  if (req.body.event === 'incident.created') {
    // Automatically restart the service
    await cloudProvider.restartService(req.body.monitor.name);
    console.log('Auto-remediation triggered for', req.body.monitor.name);
  }
  res.status(200).json({ received: true });
});

Testing Webhooks

Using the Test Button

Pingara's Send Test button sends a realistic sample event to verify connectivity. The test payload uses dummy data but follows the exact same format as real events.

Using a Request Catcher

For development, use a request inspection tool:

  1. Go to webhook.site or requestbin.com
  2. Copy the unique URL
  3. Add it as a webhook endpoint in Pingara
  4. Send a test and inspect the full request

Local Development

Use a tunnel service like ngrok to test webhooks locally:

ngrok http 3000
# Use the generated https URL as your webhook endpoint

Troubleshooting

Webhook Not Firing

  1. Check alert policy - Is the webhook channel enabled?
  2. Check triggers - Is the event type enabled (Down, Recovery, etc.)?
  3. Check monitor link - Is the policy linked to the monitor?

Receiving 4xx/5xx Errors

  • 401/403 - Authentication failed. Verify your secret/token.
  • 404 - Endpoint URL is wrong. Check the path.
  • 500 - Your server has a bug. Check server logs.
  • Timeout - Your endpoint is too slow. Respond within 10 seconds.

Payload Parsing Errors

Ensure your endpoint:

  • Sets Content-Type: application/json middleware
  • Parses the JSON body correctly
  • Handles missing optional fields gracefully

Next Steps

Related Articles

Integrations5 min

Slack Integration

Set up Slack webhook integration to receive rich, formatted Pingara alerts directly in your Slack channels with incident details and quick action links.

Alerts6 min

Alert Channels

Configure notification channels including Email, Slack, Microsoft Teams, Discord, webhooks, and PagerDuty to receive Pingara alerts wherever your team works.

Alerts6 min

Setting Up Alerts

Learn how to create alert policies, configure notification rules, and ensure your team is always informed when monitors detect issues.