Custom Webhooks
Send alerts to beeps from any system using HTTP POST requests.
Webhook Format
Section titled “Webhook Format”beeps relays accept webhook POST requests at:
https://hooks.beeps.dev/YOUR_WEBHOOK_IDBasic Example
Section titled “Basic Example”Send a simple alert:
curl -X POST https://hooks.beeps.dev/YOUR_WEBHOOK_ID \ -H "Content-Type: application/json" \ -d '{ "title": "Database Connection Failed", "message": "Unable to connect to production database", "severity": "critical" }'Payload Structure
Section titled “Payload Structure”Required Fields
Section titled “Required Fields”title(string): Short alert title (max 255 characters)
Optional Fields
Section titled “Optional Fields”message(string): Detailed alert descriptionseverity(string): Alert severity level, case-insensitivelow/infomedium/warninghigh/errorcritical/fatal- Defaults to
mediumwhen omitted. Unrecognized values also default tomedium— the alert is still created so nothing is dropped, and the response includes awarningsarray describing the problem.
source(string): System generating the alerttimestamp(string): ISO 8601 timestamp, stored with the alert’s raw payload (alert time is set at ingestion)metadata(object): Additional key-value pairs
Content types and format detection
Section titled “Content types and format detection”beeps parses the request body as JSON regardless of the Content-Type header, so curl -d without an explicit header still produces structured alerts. Bodies that aren’t valid JSON create an alert titled from the first line of the text, with a warning in the response.
beeps auto-detects known monitoring payloads (Sentry, Datadog, Axiom, Prometheus) by shape. If your custom payloads use field names that collide with those formats (like alertType or receiver), pin the webhook to the custom format so detection never rewrites your alerts:
beeps webhook set-provider --relay-id rly_abc123 --webhook-id wh_def456 --provider customPin to a specific provider (sentry, datadog, axiom, prometheus) to skip detection for that tool, or auto to restore detection.
Response
Section titled “Response”Successful deliveries return HTTP 200 with the created alert IDs:
{ "success": true, "alertsCreated": 1, "alertIds": ["alr_abc123"]}If the payload had problems that beeps worked around (like an unrecognized severity), the response includes warnings:
{ "success": true, "alertsCreated": 1, "alertIds": ["alr_abc123"], "warnings": [ "unknown severity \"sev1\"; defaulted to \"medium\". Valid values: critical, high, medium, low, info (aliases: warning, error, fatal)" ]}Batches are capped at 100 alerts per request; anything beyond the first 100 is dropped with a warning, so split large batches across requests.
Complete Example
Section titled “Complete Example”{ "title": "High API Error Rate", "message": "Production API error rate exceeded 5% threshold. Current rate: 8.2%", "severity": "critical", "source": "monitoring-system", "timestamp": "2024-10-27T10:30:00Z", "metadata": { "environment": "production", "service": "api", "region": "us-east-1", "error_rate": 8.2, "threshold": 5.0, "dashboard_url": "https://dashboard.example.com/api", "runbook_url": "https://wiki.example.com/runbooks/high-error-rate" }}Language Examples
Section titled “Language Examples”Python
Section titled “Python”import requestsfrom datetime import datetime
def send_alert(title, message, severity="high"): webhook_url = "https://hooks.beeps.dev/YOUR_WEBHOOK_ID"
payload = { "title": title, "message": message, "severity": severity, "source": "python-app", "timestamp": datetime.utcnow().isoformat() + "Z", "metadata": { "host": "app-server-01", "pid": 12345 } }
response = requests.post(webhook_url, json=payload) response.raise_for_status() return response.json()
send_alert( "Database Connection Failed", "Could not connect to PostgreSQL after 3 retries", severity="critical")Node.js
Section titled “Node.js”async function sendAlert(title, message, severity = "high") { const webhookUrl = "https://hooks.beeps.dev/YOUR_WEBHOOK_ID";
const payload = { title, message, severity, source: "nodejs-app", timestamp: new Date().toISOString(), metadata: { host: process.env.HOSTNAME, pid: process.pid, }, };
const response = await fetch(webhookUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), });
if (!response.ok) { throw new Error(`Failed to send alert: ${response.statusText}`); }
return response.json();}
await sendAlert( "High Memory Usage", "Application memory usage exceeded 90%", "warning",);package main
import ( "bytes" "encoding/json" "net/http" "time")
type Alert struct { Title string `json:"title"` Message string `json:"message"` Severity string `json:"severity"` Source string `json:"source"` Timestamp string `json:"timestamp"` Metadata map[string]interface{} `json:"metadata"`}
func sendAlert(title, message, severity string) error { webhookURL := "https://hooks.beeps.dev/YOUR_WEBHOOK_ID"
alert := Alert{ Title: title, Message: message, Severity: severity, Source: "go-app", Timestamp: time.Now().UTC().Format(time.RFC3339), Metadata: map[string]interface{}{ "host": "app-server-01", "version": "1.2.3", }, }
payload, err := json.Marshal(alert) if err != nil { return err }
resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(payload)) if err != nil { return err } defer resp.Body.Close()
return nil}
func main() { sendAlert( "Service Unavailable", "Health check failed for payment service", "critical", )}require 'net/http'require 'json'require 'uri'
def send_alert(title, message, severity: 'high') webhook_url = URI('https://hooks.beeps.dev/YOUR_WEBHOOK_ID')
payload = { title: title, message: message, severity: severity, source: 'ruby-app', timestamp: Time.now.utc.iso8601, metadata: { host: `hostname`.strip, ruby_version: RUBY_VERSION } }
http = Net::HTTP.new(webhook_url.host, webhook_url.port) http.use_ssl = true
request = Net::HTTP::Post.new(webhook_url) request['Content-Type'] = 'application/json' request.body = payload.to_json
response = http.request(request) raise "Failed: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)end
send_alert( 'Cache Miss Rate High', 'Redis cache miss rate exceeded 50%', severity: 'warning')#!/bin/bash
WEBHOOK_URL="https://hooks.beeps.dev/YOUR_WEBHOOK_ID"
send_alert() { local title="$1" local message="$2" local severity="${3:-high}"
curl -X POST "$WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d @- <<EOF{ "title": "$title", "message": "$message", "severity": "$severity", "source": "bash-script", "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "metadata": { "host": "$(hostname)", "user": "$USER", "script": "$0" }}EOF}
send_alert \ "Backup Failed" \ "Nightly database backup failed with exit code 1" \ "critical"Common Patterns
Section titled “Common Patterns”Cron Job Monitoring
Section titled “Cron Job Monitoring”#!/bin/bash
WEBHOOK_URL="https://hooks.beeps.dev/YOUR_WEBHOOK_ID"JOB_NAME="database-backup"
run_job() { # Run your job /usr/local/bin/backup-database.sh return $?}
notify_failure() { curl -X POST "$WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d @- <<EOF{ "title": "Cron Job Failed: $JOB_NAME", "message": "Job failed with exit code $1", "severity": "critical", "source": "cron", "metadata": { "job": "$JOB_NAME", "exit_code": $1, "host": "$(hostname)", "time": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" }}EOF}
if run_job; then echo "Job succeeded"else exit_code=$? notify_failure $exit_code exit $exit_codefiHealth Check Monitoring
Section titled “Health Check Monitoring”const WEBHOOK_URL = "https://hooks.beeps.dev/YOUR_WEBHOOK_ID";const SERVICES = [ { name: "api", url: "https://api.example.com/health" }, { name: "database", url: "https://db.example.com/ping" }, { name: "cache", url: "https://cache.example.com/status" },];
async function checkHealth() { for (const service of SERVICES) { try { const response = await fetch(service.url, { timeout: 5000 });
if (!response.ok) { await sendAlert({ title: `${service.name} health check failed`, message: `HTTP ${response.status}: ${response.statusText}`, severity: "critical", source: "health-check", metadata: { service: service.name, url: service.url, status_code: response.status, }, }); } } catch (error) { await sendAlert({ title: `${service.name} unreachable`, message: error.message, severity: "critical", source: "health-check", metadata: { service: service.name, url: service.url, error: error.message, }, }); } }}
async function sendAlert(payload) { await fetch(WEBHOOK_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...payload, timestamp: new Date().toISOString(), }), });}
setInterval(checkHealth, 60000);SSL Certificate Expiration Monitoring
Section titled “SSL Certificate Expiration Monitoring”import * as tls from "tls";import * as https from "https";
const WEBHOOK_URL = "https://hooks.beeps.dev/YOUR_WEBHOOK_ID";const WARNING_DAYS = 30;
const DOMAINS = ["api.example.com", "www.example.com", "app.example.com"];
interface CertificateInfo { domain: string; validTo: Date; daysRemaining: number; issuer: string; subject: string;}
async function checkCertificate(domain: string): Promise<CertificateInfo> { return new Promise((resolve, reject) => { const options = { host: domain, port: 443, method: "GET", rejectUnauthorized: false, agent: false, };
const req = https.request(options, (res) => { const cert = res.socket.getPeerCertificate();
if (!cert || Object.keys(cert).length === 0) { reject(new Error("No certificate found")); return; }
const validTo = new Date(cert.valid_to); const now = new Date(); const daysRemaining = Math.floor( (validTo.getTime() - now.getTime()) / (1000 * 60 * 60 * 24), );
resolve({ domain, validTo, daysRemaining, issuer: cert.issuer.O || "Unknown", subject: cert.subject.CN || domain, }); });
req.on("error", reject); req.end(); });}
async function sendAlert(cert: CertificateInfo) { const severity = cert.daysRemaining <= 7 ? "critical" : "warning";
const payload = { title: `SSL Certificate Expiring Soon: ${cert.domain}`, message: `SSL certificate for ${cert.domain} expires in ${cert.daysRemaining} days (${cert.validTo.toISOString()})`, severity, source: "ssl-monitor", timestamp: new Date().toISOString(), metadata: { domain: cert.domain, days_remaining: cert.daysRemaining, expiration_date: cert.validTo.toISOString(), issuer: cert.issuer, subject: cert.subject, }, };
try { const response = await fetch(WEBHOOK_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), });
if (!response.ok) { console.error(`Failed to send alert: ${response.statusText}`); } else { console.log(`Alert sent for ${cert.domain}`); } } catch (error) { console.error(`Error sending alert:`, error); }}
async function checkAllCertificates() { console.log(`Checking ${DOMAINS.length} certificates...`);
for (const domain of DOMAINS) { try { const cert = await checkCertificate(domain);
console.log( `${domain}: ${cert.daysRemaining} days remaining (expires ${cert.validTo.toLocaleDateString()})`, );
if (cert.daysRemaining <= WARNING_DAYS) { await sendAlert(cert); } } catch (error) { console.error(`Error checking ${domain}:`, error);
await fetch(WEBHOOK_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: `SSL Certificate Check Failed: ${domain}`, message: `Unable to retrieve certificate: ${error.message}`, severity: "high", source: "ssl-monitor", timestamp: new Date().toISOString(), metadata: { domain, error: error.message, }, }), }); } }}
checkAllCertificates();
setInterval(checkAllCertificates, 24 * 60 * 60 * 1000);Usage:
# Install dependenciesnpm install
# Run the monitornpx tsx ssl-monitor.ts
# Or run as a cron job (daily at 9 AM)0 9 * * * cd /path/to/monitor && npx tsx ssl-monitor.tsResponse Format
Section titled “Response Format”Successful webhook submission returns:
{ "success": true, "alertsCreated": 1, "alertIds": ["alr_abc123"]}Error Handling
Section titled “Error Handling”HTTP Status Codes
Section titled “HTTP Status Codes”200 OK: Alert received successfully401 Unauthorized: Unknown webhook key — check the webhook URL (beeps webhook list)413 Payload Too Large: Body exceeds the 10MB limit429 Too Many Requests: Rate limit exceeded — retry after theRetry-Afterheader (seconds)500 Internal Server Error: Ingestion failed — safe to retry the same delivery
Anything other than 200 means no alert was created, so configure your sender to retry on 429 and 5xx.
Example Error Response
Section titled “Example Error Response”{ "error": { "code": "invalid_webhook_key", "message": "Unknown webhook key. Check the webhook URL for this relay (beeps webhook list)." }}Best Practices
Section titled “Best Practices”- Include Context: Add relevant metadata for triage
- Use Severity: Properly categorize alert urgency
- Add Timestamps: Include when issue occurred
- Link to Resources: Add dashboard/runbook URLs
- Handle Errors: Implement retry logic with backoff
- Rate Limit: Batch alerts when possible
- Validate Payloads: Check data before sending
- Use HTTPS: Always use secure connections
Rate Limits
Section titled “Rate Limits”- 100 requests per minute per webhook key — each request can carry up to 100 alerts, so batch during storms
- Exceeding the limit returns
429 Too Many Requestswith aRetry-Afterheader (seconds); retry then and nothing is lost
What happens next
Section titled “What happens next”Once beeps receives a POST, it creates an alert and runs your relay rules. The response includes alertIds so you can track them programmatically.
Next Steps
Section titled “Next Steps”- Configure Relay Rules to route alerts to humans and AI agents
- Create Schedules to define on-call rotations
- Add Contact Methods for email and SMS notifications