calcom/cal.diy · error · BadRequestException

Webhook URL is not allowed: ${validation.error}

Error message

Webhook URL is not allowed: ${validation.error}

What it means

validateWebhookUrl runs validateUrlForSSRFSync (an SSRF guard from @calcom/platform-libraries) on the subscriberUrl; if the URL is not allowed it throws BadRequestException (HTTP 400) with the validation error. This blocks internal/loopback/private IPs and disallowed schemes to prevent SSRF via webhook callbacks.

Source

Thrown at apps/api/v2/src/modules/webhooks/utils/validate-webhook-url.ts:7

import { BadRequestException } from "@nestjs/common";
import { validateUrlForSSRFSync } from "@calcom/platform-libraries";

export function validateWebhookUrl(subscriberUrl: string): void {
  const validation = validateUrlForSSRFSync(subscriberUrl);
  if (!validation.isValid) {
    throw new BadRequestException(`Webhook URL is not allowed: ${validation.error}`);
  }
}

export function validateWebhookUrlIfChanged(
  newSubscriberUrl: string | undefined,
  existingSubscriberUrl: string | undefined
): void {
  if (newSubscriberUrl && newSubscriberUrl !== existingSubscriberUrl) {
    validateWebhookUrl(newSubscriberUrl);
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Use a publicly routable https URL for the subscriberUrl.
  2. For local testing, expose the callback via a tunnel that yields a public hostname.
  3. Ensure the URL scheme is allowed (typically https).
  4. Resolve the host externally and confirm it is not in a private range before submitting.

Example fix

// before
body.subscriberUrl = 'http://localhost:3000/hook';
// after
body.subscriberUrl = 'https://my-app.example.com/hook';
Defensive patterns

Strategy: validation

Validate before calling

import { isPublicHostname } from './net'; // caller utility
function assertSafeWebhookUrl(url: string) {
  try { const u = new URL(url); if (u.protocol !== 'https:') throw new Error('https required'); }
  catch (e) { throw new Error(`bad webhook url: ${e.message}`); }
  if (/localhost|127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|169\.254\./.test(url)) {
    throw new Error('SSRF: private/loopback URL not allowed');
  }
}
assertSafeWebhookUrl(body.subscriberUrl);

Type guard

const isProbablySafeWebhookUrl = (u: string): boolean => {
  try { const url = new URL(u); return url.protocol === 'https:' && !/(localhost|127\.|10\.|192\.168\.|169\.254\.)/.test(url.hostname); }
  catch { return false; }
};

Try / catch

try { await api.createWebhook(body); }
catch (e) {
  if (e.status === 400 && /Webhook URL is not allowed/.test(e.message)) {
    // prompt user for a public https URL, then retry
  } else throw e;
}

Prevention

When it happens

Trigger: Creating or updating any webhook with a subscriberUrl that resolves to a private/loopback/link-local address, uses a disallowed scheme, or otherwise fails the SSRF policy (e.g. http://localhost, http://127.0.0.1, http://10.x.x.x, http://169.254.169.254).

Common situations: Local development pointing webhooks at localhost; cloud metadata IP abuse; DNS that resolves internally; missing https; typos in the URL.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/87810d121cf9d748. Report an issue: GitHub.