Incoming Webhooks
Receive notifications from Runframe when incidents are created, updated, or resolved.
Overview
Incoming webhooks allow Runframe to push real-time notifications to your external systems. When events occur in Runframe, we send HTTP POST requests to your configured endpoint.
Webhook use cases
| Use case | Description |
|---|---|
| Status page updates | Automatically update your public status page |
| Analytics – Send incident metrics to your data warehouse | |
| Custom alerts – Trigger custom workflows based on incident events | |
| Audit logging – Archive incident data to external systems |
Creating an incoming webhook
Via dashboard
- Navigate to Settings → Webhooks → Incoming
- Click New Webhook
- Configure:
- Name: Descriptive name (e.g., “Status page updates”)
- Endpoint URL: Your server’s URL
- Events: Which events to send
- Secret: Optional signing secret for verification
- Click Save
Via API (coming soon)
API support for webhook management is coming soon. For now, use the dashboard.
Event types
Subscribe to specific event types:
| Event | Description | When sent |
|---|---|---|
incident.created | New incident created | When /inc create is used or incident created via dashboard |
incident.updated | Incident status changed | When status changes (investigating → identified, etc.) |
incident.resolved | Incident resolved | When /inc resolve is used |
incident.assigned | Responder assigned | When someone is assigned to an incident |
incident.escalated | Incident escalated | When escalation policy triggers |
postmortem.created | Postmortem created | When postmortem is started |
Webhook payload format
All webhook payloads follow this structure:
{
"id": "evt_abc123...",
"event": "incident.created",
"data": {
"incident": {
"id": "INC-042",
"title": "API latency spike",
"severity": "P1",
"status": "investigating",
"url": "https://app.runframe.io/incidents/INC-042"
}
},
"timestamp": "2025-01-15T10:30:00Z"
}
Payload examples
incident.created
{
"id": "evt_abc123...",
"event": "incident.created",
"data": {
"incident": {
"id": "INC-042",
"title": "API latency spike",
"description": "API response times > 5s",
"severity": "P1",
"status": "investigating",
"customerImpact": true,
"affectedServices": ["api-backend"],
"url": "https://app.runframe.io/incidents/INC-042",
"createdAt": "2025-01-15T10:30:00Z"
}
},
"timestamp": "2025-01-15T10:30:01Z"
}
incident.resolved
{
"id": "evt_def456...",
"event": "incident.resolved",
"data": {
"incident": {
"id": "INC-042",
"title": "API latency spike",
"severity": "P1",
"status": "resolved",
"resolutionSummary": "Fixed connection pool issue",
"resolvedAt": "2025-01-15T11:30:00Z",
"duration": "1 hour",
"url": "https://app.runframe.io/incidents/INC-042"
}
},
"timestamp": "2025-01-15T11:30:01Z"
}
postmortem.created
{
"id": "evt_ghi789...",
"event": "postmortem.created",
"data": {
"postmortem": {
"id": "PM-001",
"incidentId": "INC-042",
"title": "API latency spike postmortem",
"url": "https://app.runframe.io/postmortems/PM-001"
}
},
"timestamp": "2025-01-15T12:00:00Z"
}
Signature verification
Verify webhooks are from Runframe using HMAC signatures.
Enabling signatures
When creating a webhook, generate a secret. Runframe signs each payload with this secret using HMAC-SHA256.
Signature header
Each webhook request includes:
X-Runframe-Signature: sha256=SIGNATURE
X-Runframe-Timestamp: 1705309800
Verification example
import crypto from 'crypto'
function verifyWebhook(payload, signature, secret) {
const hmac = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
const expectedSignature = `sha256=${hmac}`
return signature === expectedSignature
}
// Usage in Express
app.post('/webhooks', (req) => {
const signature = req.headers['x-runframe-signature']
const payload = JSON.stringify(req.body)
if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature')
}
// Process webhook
})
import hmac
import hashlib
def verify_webhook(payload, signature, secret):
expected_signature = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return f'sha256={expected_signature}' == signature
# Usage in Flask
@app.route('/webhooks', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Runframe-Signature')
payload = json.dumps(request.json)
if not verify_webhook(payload, signature, os.environ['WEBHOOK_SECRET']):
return 'Invalid signature', 401
# Process webhook
Always verify signatures
Without signature verification, anyone can send fake webhook requests to your endpoint. Always verify signatures before processing webhook data.
Handling webhooks
Response requirements
Your webhook endpoint must:
- Return quickly – Respond within 3 seconds
- Return 200 OK – Acknowledge receipt by returning HTTP 200
- Process asynchronously – Don’t block the response
Best practices
Do:
- Process in background – Queue the payload for later processing
- Return immediately – Send 200 OK before processing
- Handle duplicates – Webhook IDs are unique; deduplicate by
idfield - Log everything – Keep audit trails of received webhooks
Don’t:
- Don’t block – Never process synchronously before responding
- Don’t ignore errors – Retry failed deliveries
- Don’t assume order – Events may arrive out of order
- Don’t forget timeouts – Runframe times out after 10 seconds
Example webhook handler
import express from 'express'
import { verifyWebhook } from './verify'
import { queueWebhook } from './queue'
const app = express()
app.post('/webhooks', express.raw({type: 'application/json'}), (req, res) => {
// Verify signature
const signature = req.headers['x-runframe-signature']
if (!verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature')
}
// Parse payload
const event = JSON.parse(req.body)
// Queue for background processing
queueWebhook(event)
// Acknowledge immediately
res.status(200).send('OK')
})
// Background worker
function processWebhook(event) {
switch (event.event) {
case 'incident.created':
updateStatusPage(event.data.incident)
break
case 'incident.resolved':
sendResolutionNotification(event.data.incident)
break
}
}
Retry behavior
If your endpoint returns a non-2xx status or times out:
| Attempt | Timing |
|---|---|
| 1 | Immediate |
| 2 | 1 minute later |
| 3 | 5 minutes later |
| 4 | 30 minutes later |
| 5 | 2 hours later |
After 5 failed attempts, the webhook is disabled and you’ll receive an email notification.
Disabling retries
For idempotent endpoints where retries aren’t needed, configure “Disable retries” in webhook settings.
Testing webhooks
Test from the dashboard
- Navigate to Settings → Webhooks → Incoming
- Click Test next to your webhook
- Runframe sends a test
incident.createdevent - Verify your endpoint receives and processes the payload
Local testing
Use tools like ngrok or localtunnel to test webhooks locally:
# Start ngrok
ngrok http 3000
# Use the ngrok URL in Runframe webhook settings
# https://abc123.ngrok.io/webhooks
Monitoring webhook health
View webhook delivery status in Settings → Webhooks → Incoming:
| Metric | Description |
|---|---|
| Success rate | Percentage of successful deliveries |
| Last delivery | Timestamp of most recent successful delivery |
| Error logs | Recent failures with error messages |
| Response time | Average time your endpoint took to respond |
Need more?
- Webhook Security – Securing your webhook endpoints
- Webhooks – Creating incidents via webhooks
- Integrations – Pre-built Datadog, Sentry, and Prometheus guides
- Web Dashboard – Webhook management UI