affaan-m/ECC · error · Error

invalid Discord webhook URL

Error message

invalid Discord webhook URL

What it means

normalizeDiscordWebhookUrl parses the webhook URL and throws 'invalid Discord webhook URL' when new URL() fails — i.e. the value is not an absolute, well-formed URL at all. This is the first of three validation stages: URL parseability, then host/protocol constraints, then path shape.

Solutions

  1. Set DISCORD_WEBHOOK_URL to the full webhook URL copied from Discord (Server Settings -> Integrations -> Webhooks -> Copy URL), including 'https://'.
  2. Trim whitespace/newlines: normalizeDiscordWebhookUrl(value.trim().replace(/\s+/g, '')).
  3. Verify the secret in the CI workflow was saved with the complete URL and not truncated by the secret editor.
  4. Check locally: node -e "new URL(process.env.DISCORD_WEBHOOK_URL)" should not throw.

Example fix

// before
const url = normalizeDiscordWebhookUrl('discord.com/api/webhooks/123/abc');
// after
const url = normalizeDiscordWebhookUrl('https://discord.com/api/webhooks/123/abc');
Defensive patterns

Strategy: validation

Validate before calling

function looksLikeWebhookUrl(v) {
  if (typeof v !== 'string') return false;
  try { return new URL(v.trim().replace(/\s+/g, '')).protocol === 'https:'; } catch { return false; }
}
if (!looksLikeWebhookUrl(process.env.DISCORD_WEBHOOK_URL)) throw new Error('DISCORD_WEBHOOK_URL must be an absolute https URL');

Type guard

const isParseableUrl = (v) => { try { new URL(typeof v === 'string' ? v.trim() : ''); return true; } catch { return false; } };

Try / catch

try {
  const url = normalizeDiscordWebhookUrl(env.DISCORD_WEBHOOK_URL);
} catch (err) {
  if (err.message === 'invalid Discord webhook URL') {
    console.error('DISCORD_WEBHOOK_URL must be a full https://discord.com/api/webhooks/... URL');
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

Trigger: main() passes an empty/undefined webhook value, a bare token without scheme ('discord.com/api/webhooks/...'), a URL with a typo in the scheme ('https:/discord.com/...'), or a non-URL string from an env var or secret store.

Common situations: DISCORD_WEBHOOK_URL env var unset or quoted incorrectly in workflow YAML; secret pasted without the leading https://; multiline paste introducing whitespace/newlines into the string.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/d76721727023c844. Report an issue: GitHub.

Appendix: source

Thrown at scripts/discord/announcement-core.mjs:58

      description,
      url: String(url || ''),
      footer: { text: footer },
    }],
  };
}

export function findDiscordReceipt(messages, key) {
  const discussionId = String(key).split(':').at(-1);
  return messages.find(message => message.embeds?.some(embed => embed.footer?.text === `ecc:${discussionId}`)) || null;
}

export function normalizeDiscordWebhookUrl(value) {
  const raw = String(value || '').trim();
  let parsed;
  try {
    parsed = new URL(raw);
  } catch {
    throw new Error('invalid Discord webhook URL');
  }
  if (parsed.protocol !== 'https:' || parsed.hostname !== 'discord.com' || parsed.port || parsed.username || parsed.password || parsed.search || parsed.hash) {
    throw new Error('invalid Discord webhook URL');
  }
  if (!/^\/api\/webhooks\/\d{10,25}\/[A-Za-z0-9._-]{20,}$/.test(parsed.pathname)) {
    throw new Error('invalid Discord webhook URL');
  }
  parsed.search = '?wait=true';
  return parsed.toString();
}

export function discussionReceiptMarker(key) {
  return `<!-- ecc-discord-receipt:${createHash('sha256').update(String(key)).digest('hex').slice(0, 32)} -->`;
}

export function findDiscussionReceipt(comments, marker) {
  const trusted = comments.filter(comment => (
    ['github-actions', 'github-actions[bot]'].includes(comment?.author?.login)

View on GitHub (pinned to 8321021c54)