Uptime Monitoring Webhooks and Integrations: Build Custom Workflows
Connect uptime monitoring to your systems via webhooks. Complete guide to incident automation, custom notifications, and workflow integration patterns.
Why Webhooks Matter for Monitoring#
Email alerts reach you eventually. Slack alerts appear in a channel. But webhooks let you do things when incidents happen.
Without webhooks:
- Alert fires → You manually click to create Jira ticket → You manually update status page → You manually page on-call engineer
- Response time: 5-10 minutes
With webhooks:
- Alert fires → Webhook triggers → Automatically creates Jira ticket → Updates status page → Pages on-call engineer
- Response time: 10 seconds
For critical infrastructure, this 5-minute difference prevents customer impact.
How Webhooks Work#
When your site goes down and monitoring detects it:
1. Nova Uptime monitoring service detects failure
2. Nova Uptime calls your webhook URL with incident data
3. Your server receives HTTP POST with:
- Domain
- Status
- Time detected
- Response time
- Previous check result
4. Your system decides what to do
- Create Jira ticket?
- Page on-call?
- Update status page?
- Post to Slack?
5. Actions execute automatically
Setting Up Webhooks#
Step 1: Create a Webhook Receiver#
Your webhook receiver is a simple HTTP endpoint that receives incident data.
Example: Express.js webhook receiver
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/uptime-incident', async (req, res) => {
const { domain, status, detectedAt, responseTime } = req.body;
console.log(`Incident detected: ${domain} is ${status}`);
// Handle the incident
await handleIncident({
domain,
status,
detectedAt,
responseTime
});
// Respond with 200 OK to acknowledge receipt
res.json({ success: true });
});
app.listen(3000, () => {
console.log('Webhook receiver listening on port 3000');
});
Step 2: Configure Webhook in Nova Uptime#
- Log into go.novauptime.com
- Domain settings → Webhooks
- Click "Add Webhook"
- Enter your endpoint URL:
https://yourdomain.com/webhooks/uptime-incident - Select events to trigger:
- ✅ Site down
- ✅ Site recovered
- ✅ Response time warning
- Save
Step 3: Test the Webhook#
Most tools have a "Test Webhook" button:
- Click "Test" in webhook settings
- Your endpoint receives test data
- Verify your system responds with 200 OK
Real-World Webhook Patterns#
Pattern 1: Create Incident Tickets#
When site goes down, automatically create a Jira ticket.
async function handleIncident({ domain, status, detectedAt }) {
if (status === 'down') {
// Create Jira ticket
const ticket = await createJiraTicket({
project: 'OPS',
issueType: 'Incident',
summary: `Production Incident: ${domain} is down`,
description: `
Domain: ${domain}
Detected: ${detectedAt}
Status: DOWN
Actions:
1. Check server status
2. Review recent deployments
3. Check error logs
`,
priority: 'Critical',
labels: ['incident', 'production']
});
console.log(`Created ticket: ${ticket.key}`);
}
}
Pattern 2: Update Status Page#
When incident happens, automatically update your public status page.
async function handleIncident({ domain, status, detectedAt }) {
if (status === 'down') {
// Create incident on status page
await createStatusPageIncident({
name: `${domain} is Down`,
status: 'investigating',
body: `We're investigating an issue with ${domain}. More info coming soon.`,
affectedComponents: [domain]
});
} else if (status === 'up') {
// Resolve incident on status page
await updateStatusPageIncident({
status: 'resolved',
body: `${domain} is now back online. We apologize for the inconvenience.`
});
}
}
Pattern 3: Page On-Call Engineer#
Send SMS to on-call person immediately on critical incidents.
async function handleIncident({ domain, status, detectedAt }) {
if (status === 'down') {
// Get current on-call engineer from PagerDuty
const oncall = await getOnCallEngineer();
// Send SMS
await sendSMS({
to: oncall.phone,
message: `CRITICAL: ${domain} is down. Incident ticket: JIRA-123`
});
// Also post to #incidents Slack channel
await postToSlack({
channel: '#incidents',
text: `@${oncall.slackHandle}: ${domain} is down. See JIRA-123`
});
}
}
Pattern 4: Store Incident History#
Log all incidents to your database for analytics.
async function handleIncident({ domain, status, detectedAt, responseTime }) {
// Store in database
const incident = await Incident.create({
domain,
status,
detectedAt,
responseTime,
createdAt: new Date(),
handledAt: new Date(),
ticketCreated: false,
statusPageUpdated: false
});
console.log(`Stored incident: ${incident._id}`);
// Later: calculate MTTR, uptime %, etc.
await updateIncidentMetrics(domain);
}
Pattern 5: Multi-Service Orchestration#
When one service fails, trigger actions across multiple platforms.
async function handleIncident({ domain, status, detectedAt }) {
if (status === 'down') {
// Parallel actions: Don't wait for each to finish
await Promise.all([
createJiraTicket({ domain, status }),
createStatusPageIncident({ domain }),
pageOnCallEngineer({ domain }),
postSlackAlert({ domain }),
storeIncidentHistory({ domain, detectedAt }),
triggerPostmortemWorkflow({ domain })
]);
}
}
Advanced Patterns#
Pattern 6: Conditional Logic Based on Severity#
Different actions for different severity levels.
async function handleIncident({ domain, status, severity }) {
if (severity === 'critical') {
// Critical: Page everyone
await pageOnCall({ priority: 'high' });
await updateStatusPage({ status: 'major_outage' });
await createJiraTicket({ priority: 'Critical' });
} else if (severity === 'warning') {
// Warning: Slack + Jira, no SMS
await postSlackAlert({ channel: '#alerts' });
await createJiraTicket({ priority: 'Medium' });
} else if (severity === 'info') {
// Info: Log only, no alert
await storeIncidentHistory({ domain });
}
}
Pattern 7: Deduplication#
Prevent duplicate tickets/alerts if same domain fails multiple times.
async function handleIncident({ domain, status }) {
if (status === 'down') {
// Check if active incident already exists
const activeIncident = await Incident.findOne({
domain,
status: 'active',
createdAfter: new Date(Date.now() - 15 * 60 * 1000) // Last 15 mins
});
if (activeIncident) {
// Incident already reported, just update
activeIncident.lastSeen = new Date();
await activeIncident.save();
console.log(`Updated existing incident: ${activeIncident._id}`);
} else {
// New incident, create everything
await createJiraTicket({ domain });
await pageOnCall({ domain });
// ... etc
}
}
}
Pattern 8: Retry Failed Webhooks#
If your webhook receiver is down, Nova Uptime should retry.
Nova Uptime configuration:
- Domain settings → Webhooks
- Click webhook
- Advanced settings → Retry Policy
- Enable: "Retry on failure"
- Max retries: 3
- Retry delay: Exponential (5s, 10s, 20s)
Your webhook receiver should be idempotent (safe to call multiple times):
// Good: Idempotent
async function handleIncident({ domain, status, eventId }) {
// Check if already processed
const processed = await ProcessedEvents.findOne({ eventId });
if (processed) {
return res.json({ success: true, cached: true });
}
// Process incident
await doActualWork();
// Record as processed
await ProcessedEvents.create({ eventId, processedAt: new Date() });
res.json({ success: true });
}
Webhook Integration Examples#
Integration 1: Zapier#
If you don't want to build custom webhooks, use Zapier:
- Nova Uptime → Zapier → Slack/Jira/Email/etc.
- No coding required
- Limitations: Less control, adds latency
Integration 2: GitHub Actions#
On incident, trigger GitHub Action (e.g., auto-scaling, rollback):
async function handleIncident({ domain, status }) {
if (status === 'down') {
// Trigger GitHub Actions workflow
await triggerGitHubAction({
repo: 'mycompany/infrastructure',
workflow: 'incident-response.yml',
inputs: {
domain,
action: 'scale-up'
}
});
}
}
Integration 3: AWS Lambda#
Use Lambda for serverless webhook handling:
# AWS Lambda function
import json
import boto3
def lambda_handler(event, context):
body = json.loads(event['body'])
domain = body['domain']
status = body['status']
if status == 'down':
# Auto-scale on AWS
ec2 = boto3.client('ec2')
ec2.start_instances(InstanceIds=['i-1234567890abcdef0'])
return {
'statusCode': 200,
'body': json.dumps({'success': True})
}
Webhook Security#
Verify Webhook Signature#
Nova Uptime signs every webhook with HMAC-SHA256. Verify before processing:
const crypto = require('crypto');
app.post('/webhooks/uptime-incident', (req, res) => {
const signature = req.headers['x-gum-signature'];
const body = JSON.stringify(req.body);
const secret = process.env.NOVAUPTIME_WEBHOOK_SECRET;
// Compute expected signature
const expected = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
if (signature !== expected) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Unauthorized' });
}
// Process webhook
handleIncident(req.body);
res.json({ success: true });
});
Rate Limiting#
Your webhook receiver should rate-limit calls:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100 // max 100 requests per minute
});
app.post('/webhooks/uptime-incident', limiter, (req, res) => {
// Handle webhook
});
Timeout Handling#
Webhook receiver should respond quickly:
app.post('/webhooks/uptime-incident', async (req, res) => {
// Respond immediately
res.json({ success: true });
// Do real work in background
setTimeout(async () => {
await handleIncident(req.body);
}, 0);
});
Testing Your Webhooks#
Test Method 1: Local Testing with ngrok#
- Start local webhook receiver on localhost:3000
- Run ngrok:
ngrok http 3000 - Get public URL:
https://abc123.ngrok.io - Configure in Nova Uptime:
https://abc123.ngrok.io/webhooks/uptime-incident - Click "Test" in Nova Uptime → See request in local console
Test Method 2: Webhook Tester#
Use webhook.site for free testing:
- Go to webhook.site
- Copy your unique URL
- Configure in Nova Uptime as webhook receiver
- Test → See request in webhook.site dashboard
Monitoring Your Webhooks#
Track webhook health:
async function monitorWebhookHealth() {
const stats = await WebhookEvent.aggregate([
{
$group: {
_id: null,
totalEvents: { $sum: 1 },
successCount: { $sum: { $cond: ['$success', 1, 0] } },
failureCount: { $sum: { $cond: ['$success', 0, 1] } },
avgResponseTime: { $avg: '$responseTime' }
}
}
]);
const successRate = stats[0].successCount / stats[0].totalEvents;
if (successRate < 0.95) {
// Alert: Webhook success rate below 95%
await alertSlack(`
Webhook health: ${(successRate * 100).toFixed(1)}% success rate
Failed events: ${stats[0].failureCount}
`);
}
}
Summary: Webhook Integration Checklist#
- ✅ Build webhook receiver endpoint
- ✅ Configure webhook in Nova Uptime settings
- ✅ Test webhook with sample data
- ✅ Verify webhook signature (HMAC-SHA256)
- ✅ Implement retry logic and idempotency
- ✅ Add rate limiting to webhook endpoint
- ✅ Set up response timeout handling
- ✅ Create incident workflow (Jira + Status Page + Slack)
- ✅ Test with real incident or forced failure
- ✅ Monitor webhook health and success rate
- ✅ Document webhook endpoints for team
Get Started Today#
Webhooks transform monitoring from alerts-only to fully automated incident response.
If you use Nova Uptime, go to domain settings and add your first webhook. Start simple: Just log incidents to your database. Then add integrations one at a time.
Webhook documentation: Nova Uptime API documentation
Monitor Your Website Before It Goes Down
Get uptime monitoring, SSL tracking, domain expiry alerts, and email health checks. Free plan — no credit card required.
Start Monitoring FreeRelated Articles
How to Integrate Uptime Monitoring with Slack: Real-Time Alerts Guide
Set up Slack alerts for website downtime in 10 minutes. Route incidents to #alerts and reduce response time from 30 minutes to 60 seconds.
Monitor Websites with AI Using Nova Uptime MCP Server
Connect Nova Uptime to AI assistants like Claude and Cursor using MCP. Set up AI-powered monitoring and email health checks in minutes.
Custom Email Alerts and Escalations: Advanced Incident Routing
Design escalation workflows that page the right person at the right time. Guide to alert routing, on-call integration, and escalation policies.