Runframe
API Reference

Webhooks API

Runframe webhooks API reference. Create incidents from Datadog, Sentry, Prometheus, CloudWatch, and Custom Webhooks. Learn payload format, authentication, and examples.

Webhooks

Create incidents automatically from external tools using webhooks.


Overview

Runframe webhooks allow you to create incidents from any monitoring tool, custom script, or third-party service. Send a POST request to Runframe with incident details and we'll create the incident and notify your team.

Webhook endpoint

https://api.runframe.io/webhooks/{routingKey}

Each integration (Datadog, Sentry, Prometheus, CloudWatch, Custom Webhook) gets a unique routing key that acts as both:

  1. Identifier - Routes webhooks to your organization
  2. Security token - Authenticates requests (like an API key)

Get your webhook URL from SettingsIntegrations in the Runframe dashboard.


Creating a webhook

Generate a webhook URL

  1. Navigate to SettingsIntegrations
  2. Click Connect for your integration (Datadog, Sentry, Prometheus, CloudWatch, or Custom Webhook)
  3. Copy the unique webhook URL (includes your routing key)
  4. Link services to route alerts to specific teams

Configure your tool

Use the webhook URL in your monitoring tool or custom script:

curl https://api.runframe.io/webhooks/abc123def456... \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "title": "API latency spike",
    "service_id": "SER-00001",
    "severity": "SEV1",
    "description": "API response times > 5s"
  }'

Webhook payload (Custom Webhook)

The Custom Webhook integration accepts a Runframe-defined JSON payload. Other integrations (Datadog, Sentry, Prometheus, CloudWatch) accept their native payload formats — Runframe parses them automatically.

Required fields

FieldTypeDescription
titlestringBrief incident summary (max 500 chars)
service_idstringService ID to route the incident to (e.g., SER-00001, max 50 chars)

Optional fields

FieldTypeDescription
descriptionstringDetailed description (max 5000 chars)
severitystringSEV0, SEV1, SEV2, SEV3, or SEV4 (default: uses fallback chain)
statusstringnew, investigating, or resolved (default: new)
source_urlstringLink back to the source (dashboard, CI run, etc.)
dedup_keystringDeduplication key — same key updates existing incident (max 255 chars)
metadataobjectKey-value string pairs for additional context (max 10 keys)

Example payloads

Minimal incident

{
  "title": "Server is down",
  "service_id": "SER-00001"
}

Complete incident

{
  "title": "Database connection pool exhaustion",
  "service_id": "SER-00002",
  "description": "Database connections exhausted. API returning 500 errors.",
  "severity": "SEV1",
  "status": "new",
  "source_url": "https://grafana.example.com/dashboard/db",
  "dedup_key": "db-pool-prod-001",
  "metadata": {
    "environment": "production",
    "region": "us-east-1"
  }
}

With deduplication

Send the same dedup_key to update an existing incident instead of creating a new one:

{
  "title": "High error rate (updated)",
  "service_id": "SER-00001",
  "severity": "SEV2",
  "dedup_key": "error-rate-api-001",
  "metadata": {
    "error_rate": "15%",
    "region": "us-east-1"
  }
}

Response format

Success response

{
  "data": {
    "incidentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "incidentNumber": "INC-042",
    "status": "investigating"
  },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2026-01-15T10:30:00Z"
  }
}

incidentId is a UUID. incidentNumber is the human-readable incident reference.

Error response

{
  "error": {
    "code": "ROUTE_NOT_FOUND",
    "message": "Webhook routing key is invalid or expired",
    "requestId": "req_abc123"
  }
}

Error codes

CodeDescription
ROUTE_NOT_FOUNDInvalid or expired routing key
INVALID_PAYLOADRequest body failed validation
RATE_LIMITEDExceeded 100 requests/min/key
SIGNATURE_INVALIDHMAC signature verification failed
TIMESTAMP_EXPIREDTimestamp outside replay window

Security

Keep webhook URLs secret

Treat webhook URLs like passwords. Anyone with the URL can create incidents in your organization.

Best practices:

  • Don't commit webhook URLs to git
  • Use environment variables for scripts
  • Regenerate webhook URLs if they're accidentally exposed
  • Disconnect unused integrations

Rate limiting

Runframe rate-limits incoming webhooks to 100 requests per minute per routing key. Exceeding the limit returns 429 Too Many Requests.

HMAC signing (Custom Webhook)

For Custom Webhooks, configure a signing secret for an additional security layer. See the Webhook Security page for details.


Testing webhooks

Test with curl

curl -X POST https://api.runframe.io/webhooks/YOUR_ROUTING_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Test webhook",
    "service_id": "SER-00001",
    "description": "Testing webhook integration"
  }'

Examples

Datadog webhook

curl https://api.runframe.io/webhooks/abc123def456... \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "title": "{{alert.name}}",
    "description": "{{alert.text}}",
    "severity": "{{alert.priority}}"
  }'

Sentry webhook

curl https://api.runframe.io/webhooks/abc123def456... \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "title": "{{event.title}}",
    "description": "{{event.culprit}}",
    "severity": "{{event.level}}"
  }'

Prometheus Alertmanager webhook

receivers:
  - name: 'runframe'
    webhook_configs:
      - url: 'https://api.runframe.io/webhooks/abc123def456...'
        send_resolved: true

CloudWatch via SNS

CloudWatch alarms are sent via SNS — no manual curl needed. Configure the SNS subscription to point to your Runframe webhook URL. See the CloudWatch integration guide for setup instructions.

Custom Webhook (Python)

import requests
import os

webhook_url = os.environ['RUNFRAME_WEBHOOK_URL']

def create_incident(title, service_id, severity='SEV2', description=''):
    payload = {
        'title': title,
        'service_id': service_id,
        'severity': severity,
        'description': description
    }
    response = requests.post(webhook_url, json=payload)
    return response.json()

# Usage
incident = create_incident(
    title='High memory usage',
    service_id='SER-00001',
    severity='SEV2',
    description='Memory usage above 90% on production servers'
)

Custom Webhook with HMAC signing (Python)

import requests
import hmac
import hashlib
import time
import json
import os

webhook_url = os.environ['RUNFRAME_WEBHOOK_URL']
signing_secret = os.environ['RUNFRAME_SIGNING_SECRET']

def create_signed_incident(payload):
    body = json.dumps(payload)
    timestamp = str(int(time.time()))
    signature = hmac.new(
        signing_secret.encode(),
        f"{timestamp}.{body}".encode(),
        hashlib.sha256
    ).hexdigest()

    response = requests.post(webhook_url, data=body, headers={
        'Content-Type': 'application/json',
        'X-Runframe-Signature': f'sha256={signature}',
        'X-Runframe-Timestamp': timestamp
    })
    return response.json()

Need more?