Developer Reference • REST Webhooks

Webhook Alerts & JSON Payload Guide

Stream real-time certificate events, expiry milestones, renewals, and DNS outages directly into your DevOps channels, Slack, Discord, or custom backend services.

Standard JSON & Auto-Presets

Clean, predictable JSON envelope. When you paste a Slack or Discord webhook URL, ExpiryHawk automatically formats the payload natively.

HMAC-SHA256 Signatures

Protect your receiver from tampering and replay attacks using cryptographically signed headers (X-ExpiryHawk-Signature).

Strict Timeouts & Retries

10-second total request timeout with automatic background retries on server errors (5xx) and built-in SSRF private-network protection.

1. Adding a Webhook in 2 Minutes

Configure your destination endpoint in ExpiryHawk settings.

  1. 1

    Navigate to Alert Channels

    Go to your Profile > Alerts Settings and click Configure under the Webhook card.

  2. 2

    Paste Target URL & Choose Format

    Enter your HTTPS endpoint. If you paste a Slack (hooks.slack.com) or Discord (discord.com/api/webhooks) URL, ExpiryHawk will automatically switch the payload preset for you.

  3. 3

    Generate a Secret & Send a Live Test

    Click Generate Secret to enable HMAC-SHA256 authentication, then click Send Test Webhook to verify connectivity before saving.

2. Supported Payload Formats

Select the format matching your ingestion receiver.

DEFAULT Standard Generic JSON Schema
Custom APIs, Zapier, n8n, AWS Lambda

Standard envelope containing event metadata, domain identifiers, and full certificate status:

{
  "id": "evt_9f82a17cb03de458",
  "event": "ssl.expiring",
  "created_at": "2026-11-01T15:30:00Z",
  "timestamp": 1793547000,
  "data": {
    "target": "https://api.example.com:443",
    "domain": "api.example.com",
    "protocol": "https",
    "port": 443,
    "monitor_type": "ssl_certificate",
    "status": "expiring",
    "milestone": "advance",
    "days_left": 7,
    "expiry_date": "2026-11-08T12:00:00Z",
    "issuer": "Let's Encrypt E6",
    "subject": "api.example.com",
    "message": "Advance Notice: The SSL certificate for https://api.example.com:443 is approaching its expiration date (7 days remaining).",
    "certificate": {
      "issuer": "Let's Encrypt E6",
      "subject": "api.example.com",
      "serial_number": "04A1B2C3D4E5F67890",
      "valid_from": "2026-08-10T12:00:00Z",
      "valid_to": "2026-11-08T12:00:00Z"
    },
    "links": {
      "domain_url": "https://expiryhawk.com/domain/42",
      "dashboard_url": "https://expiryhawk.com/dashboard"
    }
  }
}
SLACK PRESET Slack Block Kit + Attachments
Incoming Webhooks

Formatted with color-coded severity borders (🟢 Green on renewed, ⚠️ Amber on 7-day reminder, 🔴 Red on expiration/NXDOMAIN) and one-click action buttons:

{
  "text": "⚠️ [SSL Certificate Advance Notice] https://api.example.com:443 expires in 7 days",
  "attachments": [
    {
      "color": "#F59E0B",
      "blocks": [
        {
          "type": "header",
          "text": { "type": "plain_text", "text": "⚠️ [SSL Certificate Advance Notice] https://api.example.com:443", "emoji": true }
        },
        {
          "type": "section",
          "text": { "type": "mrkdwn", "text": "Advance Notice: The SSL certificate for https://api.example.com:443 will expire in 7 days." }
        },
        {
          "type": "section",
          "fields": [
            { "type": "mrkdwn", "text": "*Target:*\n`https://api.example.com:443`" },
            { "type": "mrkdwn", "text": "*Status:*\n*EXPIRING*" },
            { "type": "mrkdwn", "text": "*Days Remaining:*\n7 days" },
            { "type": "mrkdwn", "text": "*Issuer:*\nLet's Encrypt E6" }
          ]
        },
        {
          "type": "actions",
          "elements": [
            {
              "type": "button",
              "text": { "type": "plain_text", "text": "View in ExpiryHawk", "emoji": true },
              "url": "https://expiryhawk.com/domain/42",
              "style": "primary"
            }
          ]
        }
      ]
    }
  ]
}
DISCORD PRESET Discord Native Rich Embeds
Server Channel Webhooks

Formatted with clean visual embeds, inline fields, and color tags:

{
  "username": "ExpiryHawk Alerts",
  "avatar_url": "https://expiryhawk.com/assets/images/expiry-hawk-mark.png",
  "embeds": [
    {
      "title": "⚠️ [SSL Certificate Advance Notice] https://api.example.com:443",
      "description": "Advance Notice: The SSL certificate for https://api.example.com:443 is approaching expiration.",
      "url": "https://expiryhawk.com/domain/42",
      "color": 16096779,
      "fields": [
        { "name": "Target", "value": "`https://api.example.com:443`", "inline": true },
        { "name": "Status", "value": "**EXPIRING**", "inline": true },
        { "name": "Days Remaining", "value": "7 days", "inline": true },
        { "name": "Issuer", "value": "Let's Encrypt E6", "inline": true },
        { "name": "Expires At", "value": "2026-11-08 12:00 UTC", "inline": true },
        { "name": "Monitor Type", "value": "SSL Certificate", "inline": true }
      ],
      "footer": { "text": "ExpiryHawk • SSL & Domain Expiry Monitoring" },
      "timestamp": "2026-11-01T15:30:00Z"
    }
  ]
}

3. Event Types Matrix

Every webhook request includes an X-ExpiryHawk-Event header.

Event Identifier Trigger Condition Default Severity
ssl.expiring Certificate expiration within threshold (e.g. 30 days, 7 days, 1 day). Warning (Amber)
ssl.expired Certificate validity period has ended (days_left <= 0). Critical (Red)
ssl.renewed New certificate detected with renewed serial number and extended validity. Success (Green)
ssl.revoked Certificate reported as revoked by its issuing CA via OCSP or CRL. Critical (Red)
ssl.untrusted Trust failure detected (self_signed, untrusted_root, or hostname_mismatch). Critical (Red)
ssl.connection_failed TLS handshake or TCP connection to the endpoint failed after retries. Critical (Red)
domain.expiring Domain name registration expiration approaching via WHOIS/RDAP. Warning (Amber)
domain.expired Domain registration has expired. Critical (Red)
dns.nxdomain Domain resolution failure (domain does not resolve or NS missing). Critical (Red)
ping.test Triggered when clicking "Send Test Webhook" in settings. Info (Blue)

4. Verifying HMAC-SHA256 Signatures

Authenticate payloads and ensure they originated strictly from ExpiryHawk.

When a secret is configured, every outgoing HTTP POST request contains the following header:

X-ExpiryHawk-Signature: t=1793547000,v1=9b3a726ef341c28c8928498f...
  • t: Unix epoch timestamp when the webhook was signed.
  • v1: Hexadecimal HMAC-SHA256 signature calculated over the string: ${t}.${raw_payload_json} using your shared secret.
  • Replay Defense: Always reject requests where |current_time - t| > 300 (5 minutes).
Signature Verification Snippets Copy & paste into your receiver
Node.js (Express.js)
const crypto = require('crypto');

function verifyExpiryHawkWebhook(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(signatureHeader.split(',').map(kv => kv.split('=')));
  const timestamp = parseInt(parts.t, 10);
  const signature = parts.v1;

  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > toleranceSeconds) {
    throw new Error('Webhook timestamp outside allowed tolerance window.');
  }

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const trusted = Buffer.from(expected, 'hex');
  const provided = Buffer.from(signature, 'hex');

  if (trusted.length !== provided.length || !crypto.timingSafeEqual(trusted, provided)) {
    throw new Error('Invalid webhook HMAC signature.');
  }
  return true;
}
Python (FastAPI / Flask)
import hmac
import hashlib
import time

def verify_expiryhawk_webhook(raw_body: bytes, signature_header: str, secret: str, tolerance_seconds: int = 300) -> bool:
    pairs = dict(item.split("=", 1) for item in signature_header.split(","))
    timestamp = int(pairs.get("t", 0))
    provided_sig = pairs.get("v1", "")

    if abs(time.time() - timestamp) > tolerance_seconds:
        raise ValueError("Webhook timestamp is expired or outside tolerance window.")

    to_sign = f"{timestamp}.".encode("utf-8") + raw_body
    expected_sig = hmac.new(secret.encode("utf-8"), to_sign, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected_sig, provided_sig):
        raise ValueError("Invalid webhook HMAC signature.")
    return True
Go (Golang)
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "crypto/subtle"
    "encoding/hex"
    "errors"
    "fmt"
    "strconv"
    "strings"
    "time"
)

func VerifyWebhook(rawBody []byte, header string, secret string) error {
    parts := make(map[string]string)
    for _, item := range strings.Split(header, ",") {
        kv := strings.SplitN(item, "=", 2)
        if len(kv) == 2 { parts[kv[0]] = kv[1] }
    }
    tInt, err := strconv.ParseInt(parts["t"], 10, 64)
    if err != nil || time.Now().Unix()-tInt > 300 || tInt-time.Now().Unix() > 300 {
        return errors.New("timestamp drift exceeded")
    }
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(fmt.Sprintf("%d.", tInt)))
    mac.Write(rawBody)
    expected := hex.EncodeToString(mac.Sum(nil))
    if subtle.ConstantTimeCompare([]byte(expected), []byte(parts["v1"])) != 1 {
        return errors.New("invalid signature")
    }
    return nil
}

5. Delivery Guarantees & Timeouts

Understanding response codes, timeouts, and background worker queue behavior.

Response Expectations

Your endpoint must return an HTTP status code in the 2xx range (200 - 204) within 10 seconds.

💡 Pro Tip: Return HTTP 200/202 immediately upon receiving the payload, and dispatch heavy tasks (like sending emails or updating databases) asynchronously in your worker thread.

Retries & Error Handling

If your receiver returns a transient error (500, 502, 503, 504 or connection timeouts), ExpiryHawk's Minion queue will retry delivery with exponential backoff up to 3 times (+1m, +5m, +15m).

Client errors (400, 401, 403, 404) are treated as permanent configuration errors and will not be retried.

Ready to integrate your first Webhook?

Configure your Slack, Discord, or custom microservice endpoint in your alerts dashboard now.