Custom Webhooks

API & Integrations Updated Apr 4, 2026

Custom Webhooks let Chglog send alert notifications to any HTTPS endpoint you control. Unlike Slack, Teams, or Discord integrations (which use platform-specific formats), Custom Webhooks deliver a clean JSON payload that you can parse with any language or framework.

Use Custom Webhooks to build your own integrations: forward alerts to SMS providers, trigger PagerDuty incidents, post to Telegram, or feed data into your internal tools.

Setting up

  1. Go to Notifications → Webhook Integrations.
  2. Click Add Webhook and choose Custom Webhook.
  3. Enter your HTTPS endpoint URL.
  4. Optionally add a Signing Secret to verify payloads via HMAC-SHA256.
  5. Click Add, then use the Test button to verify delivery.

How it works

When a check changes state (goes down, recovers, or remains down), Chglog sends an HTTP POST request to your endpoint with the event details as a JSON body.

Request headers

HeaderDescription
Content-Typeapplication/json
User-AgentChglog/1.0
X-Chglog-EventThe event type (e.g. check.down)
X-Chglog-SignatureHMAC-SHA256 signature (only if a signing secret is configured). Format: sha256=<hex-digest>

Event types

EventTrigger
check.downCheck confirmed down (after 3-strike verification)
check.upCheck recovered after being down
check.degradedCheck is still down (reminder alert)
check.testTest notification sent from the UI

Payload format

{
  "event": "check.down",
  "title": "🔴 example.com is DOWN",
  "message": "⬇️ *example.com* is down\nhttps://example.com\nDetected at Apr 4, 2026 2:15 PM from 🇩🇪 Frankfurt, DE",
  "status": "down",
  "color": "#ef4444",
  "timestamp": "2026-04-04T14:15:00Z",
  "details_url": "https://app.chglog.com/checks/example-com",
  "check": "example.com",
  "error": "Connection timeout after 10s",
  "detected_at": "2026-04-04T14:15:00Z",
  "confirmation_results": [
    {
      "status": "down",
      "location": "Frankfurt, DE",
      "checked_at": "2026-04-04T14:15:00Z",
      "response_time": 0,
      "error": "Connection timeout"
    },
    {
      "status": "down",
      "location": "New York, US",
      "checked_at": "2026-04-04T14:15:02Z",
      "response_time": 0,
      "error": "Connection timeout"
    }
  ]
}

Field reference

FieldTypeDescription
eventstringEvent type identifier
titlestringHuman-readable alert title
messagestringFull alert message with context
statusstring|nullCheck status: down, up, degraded
colorstring|nullHex color code for the alert severity
timestampstringISO 8601 UTC timestamp
details_urlstring|nullLink to the check detail page
checkstring|nullCheck name / label
errorstring|nullError reason (for down events)
detected_atstring|nullWhen the issue was first detected
resolved_atstring|nullWhen the issue was resolved (recovery events)
duration_secondsint|nullDowntime duration in seconds (recovery events)
confirmation_resultsarray|nullMulti-location verification results

Verifying signatures (HMAC-SHA256)

If you configured a signing secret, every request includes an X-Chglog-Signature header. Use it to verify the payload was sent by Chglog and hasn't been tampered with:

  1. Read the raw request body (do not parse JSON first).
  2. Compute HMAC-SHA256 of the raw body using your signing secret.
  3. Compare the result to the signature in the header (after stripping the sha256= prefix).
  4. Use a constant-time comparison to prevent timing attacks.

Receiver examples

PHP

<?php
// webhook-receiver.php

$secret   = getenv('CHGLOG_WEBHOOK_SECRET');
$payload  = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_CHGLOG_SIGNATURE'] ?? '';

// Verify signature
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $sigHeader)) {
    http_response_code(403);
    exit('Invalid signature');
}

$data  = json_decode($payload, true);
$event = $data['event'] ?? '';

if ($event === 'check.down') {
    // Handle down alert — send SMS, create incident, etc.
    error_log("DOWN: {$data['check']} — {$data['error']}");
}

http_response_code(200);
echo json_encode(['ok' => true]);

Node.js (Express)

const crypto = require('crypto');
const express = require('express');
const app = express();

app.post('/webhook/chglog', express.raw({ type: '*/*' }), (req, res) => {
  const secret = process.env.CHGLOG_WEBHOOK_SECRET;
  const signature = req.headers['x-chglog-signature'] || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
    return res.status(403).json({ error: 'Invalid signature' });
  }

  const data = JSON.parse(req.body);
  const event = req.headers['x-chglog-event'];

  if (event === 'check.down') {
    console.log(`DOWN: ${data.check} — ${data.error}`);
    // Send SMS, page on-call, etc.
  }

  res.json({ ok: true });
});

app.listen(3000);

Python (Flask)

import hmac, hashlib, os, json
from flask import Flask, request, jsonify

app = Flask(__name__)
SECRET = os.environ['CHGLOG_WEBHOOK_SECRET']

@app.post('/webhook/chglog')
def chglog_webhook():
    payload = request.get_data()
    sig = request.headers.get('X-Chglog-Signature', '')
    expected = 'sha256=' + hmac.new(
        SECRET.encode(), payload, hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, sig):
        return jsonify(error='Invalid signature'), 403

    data = json.loads(payload)
    event = request.headers.get('X-Chglog-Event', '')

    if event == 'check.down':
        print(f"DOWN: {data['check']} — {data.get('error')}")
        # Forward to SMS, PagerDuty, etc.

    return jsonify(ok=True)

.NET (ASP.NET Core Minimal API)

using System.Security.Cryptography;
using System.Text;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/webhook/chglog", async (HttpContext ctx) =>
{
    var secret = Environment.GetEnvironmentVariable("CHGLOG_WEBHOOK_SECRET")!;
    using var reader = new StreamReader(ctx.Request.Body);
    var body = await reader.ReadToEndAsync();

    var signature = ctx.Request.Headers["X-Chglog-Signature"].FirstOrDefault() ?? "";
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var hash = Convert.ToHexStringLower(hmac.ComputeHash(Encoding.UTF8.GetBytes(body)));
    var expected = $"sha256={hash}";

    if (!CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(signature)))
    {
        ctx.Response.StatusCode = 403;
        return Results.Json(new { error = "Invalid signature" });
    }

    // Parse and handle event
    var data = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(body);
    var evt = ctx.Request.Headers["X-Chglog-Event"].FirstOrDefault();

    return Results.Json(new { ok = true });
});

app.Run();

SMS forwarding

Custom Webhooks are ideal for forwarding alerts to SMS services. Here are recipes for popular providers.

Twilio (PHP)

<?php
// After verifying the signature (see PHP example above):
$data = json_decode($payload, true);

if ($data['event'] === 'check.down') {
    $ch = curl_init('https://api.twilio.com/2010-04-01/Accounts/'
        . getenv('TWILIO_SID') . '/Messages.json');
    curl_setopt_array($ch, [
        CURLOPT_POST       => true,
        CURLOPT_USERPWD    => getenv('TWILIO_SID') . ':' . getenv('TWILIO_TOKEN'),
        CURLOPT_POSTFIELDS => http_build_query([
            'From' => getenv('TWILIO_FROM'),
            'To'   => getenv('ONCALL_PHONE'),
            'Body' => "🔴 {$data['check']} is DOWN: {$data['error']}",
        ]),
        CURLOPT_RETURNTRANSFER => true,
    ]);
    curl_exec($ch);
    curl_close($ch);
}

Twilio (Node.js)

// After verifying the signature (see Node.js example above):
if (event === 'check.down') {
  const twilio = require('twilio')(process.env.TWILIO_SID, process.env.TWILIO_TOKEN);
  await twilio.messages.create({
    from: process.env.TWILIO_FROM,
    to:   process.env.ONCALL_PHONE,
    body: `🔴 ${data.check} is DOWN: ${data.error}`
  });
}

Vonage / MessageBird

The same pattern works with any SMS API. After verifying the signature, extract the alert details and call the provider's HTTP API. Vonage and MessageBird both accept simple POST requests with to, from, and text fields.

Other integration ideas

  • PagerDuty — POST to the PagerDuty Events API v2 with routing_key, event_action: "trigger", and the check details as the summary.
  • Telegram Bot — Call https://api.telegram.org/bot{token}/sendMessage with your chat ID and the alert message.
  • Email-to-SMS gateways — Many carriers support email-to-SMS (e.g. 5551234567@txt.att.net). Forward the alert body via your mail server.
  • Incident management — Feed alerts into Opsgenie, Incident.io, or your own ticketing system via their webhook/API endpoints.

Security best practices

  • Always verify the signature — never process unsigned payloads if you configured a secret.
  • Use HTTPS — Chglog only sends to HTTPS endpoints. Never expose a plaintext HTTP receiver.
  • Respond quickly — return a 2xx response within 10 seconds. Offload heavy processing to a queue.
  • Store secrets securely — use environment variables, not hardcoded strings.