Security

SSL Certificate Monitoring API: Track Expiry, Changes and Alerts Programmatically

Detect certificate expiry before users do, catch unauthorized replacements, and integrate SSL health into any pipeline — with the WhoisJSON ssl certificate monitoring api.

April 24, 202611 min readSecurity · SSL · Monitoring · Python · Node.js

Introduction

An expired SSL certificate is not a minor inconvenience — it is a service outage. Modern browsers block HTTPS connections to expired certificates with a full-screen interstitial; search engines derank pages that return TLS errors; payment providers reject API calls over untrusted channels. The consequences arrive without warning and compound quickly: customer support escalations, revenue loss during downtime, and SEO recovery measured in weeks, not hours.

Manual checks do not scale. A wildcard certificate covering thirty subdomains, a certificate on a staging environment managed by a different team, a third-party SaaS endpoint embedded in your product — any of them can expire without your monitoring system noticing. The same problem applies to unexpected certificate replacements: a certificate silently swapped to a different CA is an incident waiting to be discovered.

The reliable approach is programmatic: query the ssl checker api on a schedule, parse the expiry date, compare fingerprints against a stored baseline, and fire alerts when thresholds are crossed. This guide covers how to monitor ssl programmatically using the WhoisJSON API — from a single expiry check to a change detection pipeline that catches unauthorized certificate replacements.

What Is SSL Certificate Monitoring via API?

A one-shot SSL checker tells you the current state of a certificate: is it valid, when does it expire, who issued it. Useful for a quick diagnostic, but useless for automation. An ssl certificate monitoring api adds the time dimension: query the same endpoint on a schedule, compare results against a baseline, and act when something changes or when a threshold is crossed.

Two distinct operations are at the core of any monitoring workflow:

  • Expiry monitoring: parse the valid_to  field from the API response, compute days remaining, and trigger a tiered ssl expiry alert when thresholds are breached (typically 30 days, 7 days, and 1 day). The valid  boolean tells you whether the certificate is already expired or the chain is broken — check this first before computing days.
  • Change detection: store the fingerprint256  value from the most recent check. On every subsequent run, compare the live fingerprint against the stored value. A mismatch means the certificate has been replaced — expected during renewal, suspicious if unannounced.

The WhoisJSON /ssl-cert-check  endpoint returns the full certificate object in a single structured JSON response: expiry dates in ISO 8601, issuer identity, Subject Alternative Names (SANs), key size, SHA-1 and SHA-256 fingerprints, and the full certificate details object. One API call per domain covers both use cases.

Key Use Cases

  • Pre-expiry alerting (J-30, J-7, J-1): alert at configurable thresholds before expiry, escalating severity as the deadline approaches. Critical-path certificates — checkout page, API gateway, login endpoint — warrant shorter lead times and higher-priority escalation channels than internal tooling.
  • SSL change detection: compare fingerprint256  between runs. Unexpected changes indicate either a renewal (legitimate) or an unauthorized replacement — a potential MITM attack, a misconfiguration that installed the wrong certificate, or a hijacked domain. Knowing which CA changed and which SANs are now covered narrows the investigation immediately.
  • Fleet-wide SSL audit: run the check across every domain and subdomain in your portfolio in parallel. A sweep of 500 certificates runs in seconds. Use the results to build a fleet health dashboard, a compliance report, or a pre-release certificate readiness gate.
  • CI/CD pipeline gate: query the SSL endpoint as a deployment step. Verify that the target certificate is valid, covers the expected SANs, and was issued by an approved CA before marking a release as complete. Catch misconfigurations before traffic shifts.
  • SOC enrichment: enrich security alerts with certificate data. When an alert fires on a suspicious domain, an automated playbook can call the ssl checker api and attach the issuer, SANs, fingerprint, and expiry to the incident. SSL signals — issuer identity, SAN coverage, certificate issuance timing relative to domain registration — are also a core input in phishing domain detection pipelines, where a free CA certificate provisioned within hours of registration is a high-signal risk indicator.

How to Monitor SSL Certificates with WhoisJSON API

The endpoint is GET https://whoisjson.com/api/v1/ssl-cert-check  with a single required parameter: domain. Authentication uses the Authorization: TOKEN=YOUR_API_KEY  header. No additional parameters are required.

curlShell
curl -X GET "https://whoisjson.com/api/v1/ssl-cert-check?domain=whoisjson.com" \
  -H "Authorization: TOKEN=YOUR_API_KEY"

JSON response with the fields relevant to monitoring:

response.jsonJSON
{
  "domain":     "whoisjson.com",
  "issuer": {
    "C":  "US",
    "O":  "Let's Encrypt",
    "CN": "R11"
  },
  "valid_from": "2026-02-15T10:22:31.000Z",
  "valid_to":   "2026-05-16T10:22:30.000Z",
  "valid":      true,
  "details": {
    "subject":        { "CN": "whoisjson.com" },
    "subjectaltname": "DNS:whoisjson.com, DNS:www.whoisjson.com",
    "bits":           2048,
    "serialNumber":   "03213ECA313DE36923BDF7C3FFB02AFA12ED",
    "fingerprint":    "33:9F:69:28:A0:88:62:C7:80:EB:D5:A5:06:8C:97:0A:87:35:AF:BE",
    "fingerprint256": "85:70:14:19:D2:F6:ED:7A:9E:22:1C:A1:4B:99:3D:5E:...",
    "valid_from":     "Feb 15 10:22:31 2026 GMT",
    "valid_to":       "May 16 10:22:30 2026 GMT"
  }
}

Key fields for monitoring workflows:

  • valid_to  (top-level) — ISO 8601 expiry timestamp. Parse this to compute days remaining. Use the top-level field, not details.valid_to, which uses a legacy human-readable format.
  • valid  — boolean. Already false  when the certificate is expired, self-signed, or chain validation has failed. Check this first before computing days.
  • issuer.O  — Certificate Authority name (e.g. "Let's Encrypt", "DigiCert Inc", "Sectigo Limited"). Use this to verify the certificate was issued by an approved CA and to detect unexpected CA changes between runs.
  • details.subjectaltname  — comma-separated list of hostnames this certificate covers. Verify expected hostnames are present, especially after a renewal or domain migration.
  • details.fingerprint256  — SHA-256 fingerprint. This value changes every time a new certificate is issued, even for the same domain. Store it after each check and compare on every subsequent run to detect certificate replacements.

In security pipelines, certificate data is often cross-referenced with domain registration dates from /whois. Knowing whether a certificate was provisioned on the same day the domain was registered is a high-signal indicator — this correlation relies on RDAP-enriched registration fields. The WHOIS vs RDAP primer  explains which fields are available from each protocol and why the source  field in the WhoisJSON response matters when correlating data across endpoints.

Note on caching.  The WhoisJSON API caches SSL responses for 3 hours. For expiry monitoring — where you typically poll once or twice per day — this has no practical impact. For immediate incident response, append &_forceRefresh=1  to bypass the cache (Pro plan and above; counts as 2 credits).

Code Examples

Example 1 — SSL expiry monitor (Python)

A self-contained script that checks a list of domains and returns an alert for any certificate expiring within a configurable threshold. Status is tiered: expired  when valid  is falsecritical  (≤ 1 day), warning  (≤ 7 days), and alert  (≤ threshold).

ssl_monitor.pyPython
# pip install requests
import json
import requests
from datetime import datetime, timezone

API_KEY  = "YOUR_API_KEY"
BASE_URL = "https://whoisjson.com/api/v1/ssl-cert-check"
HEADERS  = {"Authorization": f"TOKEN={API_KEY}"}


def check_ssl(session: requests.Session, domain: str) -> dict:
    resp = session.get(
        BASE_URL,
        params={"domain": domain},
        headers=HEADERS,
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()


def days_until_expiry(valid_to: str) -> int:
    expiry = datetime.fromisoformat(valid_to.replace("Z", "+00:00"))
    return (expiry - datetime.now(timezone.utc)).days


def ssl_status(valid: bool, days: int, threshold: int) -> str:
    if not valid:         return "expired"
    if days <= 1:         return "critical"
    if days <= 7:         return "warning"
    if days <= threshold: return "alert"
    return "ok"


def monitor_ssl(domains: list[str], threshold: int = 30) -> list[dict]:
    alerts = []
    with requests.Session() as session:
        for domain in domains:
            try:
                data   = check_ssl(session, domain)
                days   = days_until_expiry(data["valid_to"])
                status = ssl_status(data["valid"], days, threshold)
                if status != "ok":
                    alerts.append({
                        "domain":  domain,
                        "status":  status,
                        "days":    days,
                        "expiry":  data["valid_to"],
                        "issuer":  data["issuer"].get("O", "Unknown"),
                    })
            except Exception as exc:
                alerts.append({"domain": domain, "error": str(exc)})
    return alerts


if __name__ == "__main__":
    domains = ["example.com", "api.example.com", "staging.example.com"]
    results = monitor_ssl(domains, threshold=30)
    print(json.dumps(results, indent=2))
    # [
    #   { "domain": "staging.example.com", "status": "warning",
    #     "days": 5, "expiry": "2026-04-29T...", "issuer": "Let's Encrypt" }
    # ]

Example 2 — Batch SSL check (Node.js)

A parallel batch check using Promise.allSettled. Each result includes days remaining, a tiered status, issuer name, and the SHA-256 fingerprint for downstream ssl change detection storage. Domains that fail are isolated — one timeout does not abort the sweep.

ssl_batch.mjsNode.js
const API_KEY  = 'YOUR_API_KEY';
const BASE_URL = 'https://whoisjson.com/api/v1/ssl-cert-check';

async function checkSSL(domain) {
    const controller = new AbortController();
    const t = setTimeout(() => controller.abort(), 10_000);
    try {
        const res = await fetch(
            `${BASE_URL}?domain=${encodeURIComponent(domain)}`,
            { headers: { Authorization: `TOKEN=${API_KEY}` }, signal: controller.signal }
        );
        clearTimeout(t);
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return await res.json();
    } catch (err) { clearTimeout(t); throw err; }
}

function daysLeft(validTo) {
    return Math.floor((new Date(validTo) - Date.now()) / 86_400_000);
}

function sslStatus(valid, days, threshold = 30) {
    if (!valid)            return 'expired';
    if (days <= 1)         return 'critical';
    if (days <= 7)         return 'warning';
    if (days <= threshold) return 'alert';
    return 'ok';
}

async function monitorBatch(domains, threshold = 30) {
    const settled = await Promise.allSettled(
        domains.map(async (domain) => {
            const d    = await checkSSL(domain);
            const days = daysLeft(d.valid_to);
            return {
                domain,
                status: sslStatus(d.valid, days, threshold),
                days,
                expiry: d.valid_to,
                issuer: d.issuer?.O ?? 'Unknown',
                fp256:  d.details?.fingerprint256 ?? null,
            };
        })
    );

    return settled.map((r, i) =>
        r.status === 'fulfilled'
            ? r.value
            : { domain: domains[i], error: r.reason?.message }
    );
}

// --- Usage ---
const domains = ['example.com', 'api.example.com', 'staging.example.com'];
const report  = await monitorBatch(domains, 30);

// Print only domains that need attention
report
    .filter(r => r.error || r.status !== 'ok')
    .forEach(r => console.log(r));

SSL Change Detection: How to Detect Certificate Replacement

Expiry monitoring catches certificates before they expire. SSL change detection catches certificates that were replaced without notice — whether a legitimate renewal you need to audit or a potentially malicious replacement.

The mechanism is straightforward: store the fingerprint256  value from the first successful check. On every subsequent run, compare the live fingerprint against the stored value. A mismatch triggers a change event. The comparison costs nothing — the only API call is the regular SSL check already running.

When does fingerprint256  change?   Any time a new certificate is issued: routine renewal, migration to a different CA, re-issuance after a key compromise, or a deployment error that installed the wrong certificate. A certificate renewed by the same CA for the same domain will still produce a different fingerprint. Not all changes are malicious — but all unexpected changes warrant investigation, especially if issuer.O  has also changed.

Python change detection using a local JSON file as the fingerprint store:

ssl_change_detect.pyPython
import json, os, requests
from datetime import datetime, timezone

STORE = "ssl_fingerprints.json"

def load_store() -> dict:
    return json.load(open(STORE)) if os.path.exists(STORE) else {}

def save_store(store: dict) -> None:
    with open(STORE, "w") as f:
        json.dump(store, f, indent=2)

def detect_cert_change(domain: str, session: requests.Session) -> dict | None:
    """Returns a change report when fingerprint256 differs from stored baseline."""
    data  = check_ssl(session, domain)       # reuse check_ssl() from Example 1
    fp    = data["details"]["fingerprint256"]
    store = load_store()
    prev  = store.get(domain)

    if prev is None:
        store[domain] = {
            "fingerprint256": fp,
            "issuer":         data["issuer"].get("O"),
            "checked_at":     datetime.now(timezone.utc).isoformat(),
        }
        save_store(store)
        return None  # baseline recorded, no change to report

    if fp == prev["fingerprint256"]:
        return None  # certificate unchanged

    # Certificate has been replaced — emit report and update baseline
    report = {
        "domain":          domain,
        "previous_fp256":  prev["fingerprint256"],
        "current_fp256":   fp,
        "previous_issuer": prev["issuer"],
        "current_issuer":  data["issuer"].get("O"),
        "new_expiry":      data["valid_to"],
        "new_sans":        data["details"].get("subjectaltname"),
    }
    store[domain] = {
        "fingerprint256": fp,
        "issuer":         data["issuer"].get("O"),
        "checked_at":     datetime.now(timezone.utc).isoformat(),
    }
    save_store(store)
    return report

# --- Usage ---
with requests.Session() as session:
    changes = [
        r for domain in ["example.com", "api.example.com"]
        if (r := detect_cert_change(domain, session)) is not None
    ]
print(json.dumps(changes, indent=2))

In production, replace the local JSON file with a database row or a key-value store. The logic is identical: load the previous fingerprint, compare, write the new value if changed, and emit a change event when a mismatch is detected.

For security-critical use cases — detecting MITM attacks or certificate hijacking — combine the fingerprint check with issuer validation. If current_issuer  differs from an approved CA allowlist, escalate the alert regardless of whether the certificate itself is technically valid.

SSL change detection is one layer of a complete domain security posture. If you are also monitoring WHOIS registrant changes, nameserver replacements, and DNS record drift alongside certificate changes, the domain monitoring baseline for security teams  covers how to build the full picture.

Integrating SSL Monitoring into Your Workflow

Three integration patterns cover the majority of production use cases for monitoring ssl programmatically.

Cron job

Run the monitoring script daily. Twice daily for certificates expiring within the next 7 days. A cron at 08:00 UTC ensures alerts land at the start of the business day with enough time to act.

crontabShell
# Daily SSL check at 08:00 UTC
0 8 * * * /usr/bin/python3 /opt/ssl-monitor/ssl_monitor.py >> /var/log/ssl_monitor.log 2>&1

Webhook to Slack or PagerDuty

Extend the Python monitor to fire a webhook when an alert is triggered:

ssl_notify.pyPython
import requests as req

SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

EMOJI = {
    "expired":  ":red_circle:",
    "critical": ":rotating_light:",
    "warning":  ":warning:",
    "alert":    ":large_yellow_circle:",
}

def notify_slack(alert: dict) -> None:
    emoji = EMOJI.get(alert.get("status", "alert"), ":bell:")
    text  = (
        f"{emoji} *SSL alert — {alert['domain']}*\n"
        f"Status: `{alert.get('status')}`  |  "
        f"Days left: `{alert.get('days', 'N/A')}`  |  "
        f"Issuer: `{alert.get('issuer', 'Unknown')}`\n"
        f"Expires: `{alert.get('expiry', 'N/A')}`"
    )
    req.post(SLACK_WEBHOOK, json={"text": text}, timeout=5)

for alert in monitor_ssl(["example.com", "api.example.com"]):
    notify_slack(alert)

Three-tier alert ladder

Calibrate severity to operational impact. A 30-day notice is informational — a ticket in the backlog. A 7-day notice requires engineer assignment within hours. A 1-day notice is an emergency that should page on-call regardless of time.

ThresholdStatusRecommended action
≤ 30 daysalertCreate a ticket; assign to the certificate owner
≤ 7 dayswarningAssign immediately; escalate if unacknowledged after 4 hours
≤ 1 daycriticalPage on-call; initiate emergency renewal
0 / invalid chainexpiredIncident declared; all-hands until resolved

Conclusion

SSL certificate failures are preventable. The ssl certificate monitoring api pattern is straightforward: query the WhoisJSON /ssl-cert-check  endpoint on a schedule, parse valid_to  to compute days remaining, compare fingerprint256  against a stored baseline, and fire tiered alerts when thresholds are crossed. The same API call handles both expiry monitoring and ssl change detection.

At scale, run checks in parallel across your entire domain fleet. One request per domain returns the full certificate object — expiry dates, issuer identity, SANs, and fingerprint — in a single JSON response. Handle failures per-domain so a single timeout does not abort the entire sweep, and store fingerprints persistently to enable change detection across runs.

Start for Free

SSL checker API with 1,000 free requests/month — no credit card. All endpoints included.

Get Your Free API Key

API Reference

Full SSL endpoint documentation with all response fields.

SSL API Documentation
SSL Certificate API

Monitor SSL Certificates Programmatically

Expiry dates, issuer identity, SANs, SHA-256 fingerprint — one request, structured JSON. 1,000 free checks/month.

ISO 8601 expiry dateSHA-256 fingerprint for change detectionIssuer and SAN coverage1,000 free requests/month

Get Started Free

No credit card. 1,000 SSL checks/month, all endpoints.

Get Free API Key

Scale Up

From 40 RPM on Pro to 900 RPM on Atlas — match your plan to your workload.

Compare Plans