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_tofield 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). Thevalidboolean tells you whether the certificate is already expired or the chain is broken — check this first before computing days. - Change detection: store the
fingerprint256value 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
fingerprint256between 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.
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:
{
"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, notdetails.valid_to, which uses a legacy human-readable format.valid— boolean. Alreadyfalsewhen 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.
&_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 false, critical (≤ 1 day), warning (≤ 7 days), and alert (≤ threshold).
# 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.
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.
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:
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.
# 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:
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.
| Threshold | Status | Recommended action |
|---|---|---|
| ≤ 30 days | alert | Create a ticket; assign to the certificate owner |
| ≤ 7 days | warning | Assign immediately; escalate if unacknowledged after 4 hours |
| ≤ 1 day | critical | Page on-call; initiate emergency renewal |
| 0 / invalid chain | expired | Incident 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