n8n-io/n8n · error · Error

Webhook request failed: ${response.status} ${response.status

Error message

Webhook request failed: ${response.status} ${response.statusText}

What it means

The webhook delivery `fetch` returned a non-2xx HTTP status; the thrown message includes `response.status` and `response.statusText`. Anything from the receiver — auth failure, payload rejected, server error — surfaces here. The URL is masked in logs just above the throw, so secrets in the path/query are not leaked. Note this only covers HTTP-level failures; network errors throw from `fetch` itself and are not wrapped.

Source

Thrown at packages/@n8n/ai-workflow-builder.ee/evaluations/cli/webhook.ts:259

		logger.info('Webhook request will be signed with HMAC-SHA256');
	} else {
		logger.warn(
			'No webhook secret provided - request will not be signed. ' +
				'Consider using --webhook-secret for production use.',
		);
	}

	// Log masked URL to avoid exposing potential tokens in path/query
	logger.info(`Sending results to webhook: ${maskWebhookUrl(webhookUrl)}`);

	const response = await fetch(webhookUrl, {
		method: 'POST',
		headers,
		body,
	});

	if (!response.ok) {
		throw new Error(`Webhook request failed: ${response.status} ${response.statusText}`);
	}

	logger.info(`Webhook notification sent successfully (status: ${response.status})`);
}

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Match the status code to the cause: 401/403 → fix `--webhook-secret` / signature header name; 404 → fix URL; 4xx body → align payload schema; 5xx → check receiver health.
  2. Verify the receiver logs the incoming `X-Webhook-Signature` / `X-Webhook-Timestamp` headers and validates them with the same HMAC scheme `generateWebhookSignature` uses (`sha256=<hex>`).
  3. Reproduce with curl using the masked URL plus the same body to confirm whether the issue is the client or the receiver.

Example fix

// before
// receiver returns 401 because it expects a different header
const response = await fetch(webhookUrl, {method:'POST', headers, body});
if (!response.ok) throw new Error(`Webhook request failed: ${response.status} ${response.statusText}`);
// after
// align header names with receiver (e.g. X-Hub-Signature-256) and re-sign
headers['X-Hub-Signature-256'] = generateWebhookSignature(signaturePayload, webhookSecret);
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await sendWebhookResult({ webhookUrl, webhookSecret, payload, logger });
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Webhook request failed:')) {
    const status = e.message;
    if (status.includes('401') || status.includes('403')) {
      logger.error('webhook auth failed — check --webhook-secret and receiver signature scheme');
    } else if (status.startsWith('Webhook request failed: 5')) {
      // receiver-side fault: bounded retry with backoff is reasonable
      await backoffRetry(() => sendWebhookResult({ webhookUrl, webhookSecret, payload, logger }), { tries: 3 });
    } else {
      logger.error(`webhook delivery failed: ${status}`);
    }
    return; // do not fail the whole eval run on a webhook error
  }
  throw e;
}

Prevention

When it happens

Trigger: `fetch(webhookUrl, {method:'POST',...})` resolves but `response.ok` is false. Concretely: 401/403 (missing or wrong `--webhook-secret`), 404 (wrong URL/path), 422 (schema mismatch in `WebhookPayload`), 5xx (receiver down).

Common situations: Receiver expects a different signature scheme than HMAC-SHA256; the receiver's payload schema changed but the eval still sends the old shape; the URL drifted; rate limiting returns 429; an authenticated proxy in front of the receiver rejects the unsigned request.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/604f305a537a8e29. Report an issue: GitHub.