Skip to Content
API ReferenceIncoming Webhooks

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 caseDescription
Status page updatesAutomatically 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

  1. Navigate to SettingsWebhooksIncoming
  2. Click New Webhook
  3. 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
  4. 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:

EventDescriptionWhen sent
incident.createdNew incident createdWhen /inc create is used or incident created via dashboard
incident.updatedIncident status changedWhen status changes (investigating → identified, etc.)
incident.resolvedIncident resolvedWhen /inc resolve is used
incident.assignedResponder assignedWhen someone is assigned to an incident
incident.escalatedIncident escalatedWhen escalation policy triggers
postmortem.createdPostmortem createdWhen 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:

  1. Return quickly – Respond within 3 seconds
  2. Return 200 OK – Acknowledge receipt by returning HTTP 200
  3. 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 id field
  • 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:

AttemptTiming
1Immediate
21 minute later
35 minutes later
430 minutes later
52 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

  1. Navigate to SettingsWebhooksIncoming
  2. Click Test next to your webhook
  3. Runframe sends a test incident.created event
  4. 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 SettingsWebhooksIncoming:

MetricDescription
Success ratePercentage of successful deliveries
Last deliveryTimestamp of most recent successful delivery
Error logsRecent failures with error messages
Response timeAverage time your endpoint took to respond

Need more?

Last updated on