koala73/worldmonitor · warning · Error

Webhook URL must use HTTPS

Error message

Webhook URL must use HTTPS

What it means

createApiKey() gates on getCurrentClerkUser() (src/services/clerk.ts:833), which returns the active Clerk session user or null. If no Clerk user is present, key creation stops before any plaintext key is minted and before any Convex call. This is a deliberate UX guard, not a network or backend failure.

Solutions

  1. Ensure the user is signed in and Clerk has fully loaded before enabling the create-key action
  2. Check ClerkProvider is mounted with a valid publishable key and the session has not expired
  3. Re-sign-in and retry; if it persists, inspect getCurrentClerkUser() in devtools
  4. Disable the create button when the auth state reports no user so the call is never made

Example fix

// before
await createApiKey(name); // throws 'Sign in to create an API key.' when signed out

// after
const user = getCurrentClerkUser();
if (!user?.id) {
  openSignIn();
  return;
}
await createApiKey(name);
Defensive patterns

Strategy: validation

Validate before calling

const user = getCurrentClerkUser();
if (!user?.id) { openSignIn(); return; }
await createApiKey(name);

Type guard

const hasActiveClerkUser = (u: { id: string } | null | undefined): u is { id: string } =>
  typeof u?.id === 'string' && u.id.length > 0;

Try / catch

try {
  await createApiKey(name);
} catch (e) {
  if (e instanceof Error && e.message === 'Sign in to create an API key.') openSignIn();
  else toast(e instanceof Error ? e.message : String(e));
}

Prevention

When it happens

Trigger: Invoking createApiKey(name) while signed out or after the Clerk session expired; calling it during app boot before Clerk finishes loading (race); Clerk publishable key missing so Clerk never yields a user; user signed out in another tab sharing the session.

Common situations: API-key settings UI rendered from cached state after logout; dev server started without Clerk env vars; silent token refresh failure leaving getCurrentClerkUser() null while the UI still shows controls.

Related errors


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

Appendix: source

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

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 7d06c8633d)