koala73/worldmonitor · error · Error

Webhook URL must use HTTPS

Error message

Webhook URL must use HTTPS

What it means

Returned by blockedNotificationWebhookUrlReason when the parsed URL's protocol is anything other than `https:`. Webhook payloads can carry alert content, so the registration gate refuses cleartext http:// (and any other scheme) to prevent interception and to keep the SSRF surface consistent.

Source

Thrown at api/_notification-webhook-ssrf.ts:223

async function defaultResolveHostname(hostname: string): Promise<string[]> {
  const records = await Promise.all([
    resolveDnsJson(hostname, 'A'),
    resolveDnsJson(hostname, 'AAAA'),
  ]);
  return records.flat();
}

/**
 * Fail fast at registration when the webhook hostname currently resolves to a
 * private or reserved address. Delivery repeats this check (and pins its
 * connection) because DNS can change after registration.
 */
export async function assertNotificationWebhookRegistrationUrlSafe(
  rawUrl: string,
  resolveHostname: ResolveHostname = defaultResolveHostname,
): Promise<void> {
  const staticError = blockedNotificationWebhookUrlReason(rawUrl);
  if (staticError) throw new Error(staticError);

  const hostname = new URL(rawUrl).hostname.toLowerCase();
  if (isIpLiteral(hostname)) return;
  let resolvedAddresses: string[];
  try {
    resolvedAddresses = await resolveHostname(hostname);
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    throw new Error(`Webhook URL DNS resolution failed: ${message}`);
  }
  if (!resolvedAddresses.length) throw new Error('Webhook URL DNS resolution returned no addresses');
  if (resolvedAddresses.some(isBlockedNotificationResolvedAddress)) {
    throw new Error('Webhook URL must not point to a private/local address');
  }
}

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Register an https:// URL — terminate TLS at the webhook target (Let's Encrypt, Caddy, or a platform-managed cert).
  2. For local testing, expose the local server over TLS via a tunnel (e.g. ngrok/cloudflared with https) before registering.
  3. Update the registration UI to force https and reject http submissions before they reach the API.

Example fix

// before
registerWebhook('http://localhost:9000/hook')
// after
registerWebhook('https://my-tunnel.example.dev/hook')
Defensive patterns

Strategy: validation

Validate before calling

function isHttpsUrl(rawUrl: string): boolean {
  try { return new URL(rawUrl).protocol === 'https:'; } catch { return false; }
}

if (!isHttpsUrl(input)) return res.status(400).json({ error: 'Webhook URL must use HTTPS.' });

Type guard

function isHttpsWebhookUrl(value: unknown): value is string {
  if (typeof value !== 'string') return false;
  try { return new URL(value).protocol === 'https:'; } catch { return false; }
}

Try / catch

try {
  await assertNotificationWebhookRegistrationUrlSafe(rawUrl);
} catch (err) {
  if (err.message === 'Webhook URL must use HTTPS') {
    return res.status(400).json({ error: 'Webhook URL must use HTTPS. Use an https:// URL.' });
  }
  throw err;
}

Prevention

When it happens

Trigger: A webhook registration URL whose scheme is `http:`, `ftp:`, `file:`, or any non-https protocol passes URL parsing but fails this check at api/_notification-webhook-ssrf.ts:169.

Common situations: Local dev webhook pointing at http://localhost; an internal/staging integration that has not been issued a TLS cert; a typo leaving the scheme off so the form defaults to http.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/45550d68e61728ee. Report an issue: GitHub.