knadh/listmonk · error

invalid azure event grid shared secret

Error message

invalid azure event grid shared secret

What it means

verifyAuth compares the configured shared secret against the value supplied in the webhook request — either the query parameter (querySecretParam) or the configured header. This error is returned when neither location carries a value equal to the configured shared secret, so the Azure Event Grid delivery is rejected as unauthenticated.

Source

Thrown at internal/bounce/webhooks/azure.go:170

	if req == nil {
		return errors.New("missing azure event grid request context")
	}

	querySecret := strings.TrimSpace(req.URL.Query().Get(querySecretParam))
	if secretsEqual(a.sharedSecret, querySecret) {
		return nil
	}

	headerName := a.sharedSecretHeader
	if headerName == "" {
		headerName = defaultSecretHeader
	}
	headerSecret := strings.TrimSpace(req.Header.Get(headerName))
	if secretsEqual(a.sharedSecret, headerSecret) {
		return nil
	}

	return errors.New("invalid azure event grid shared secret")
}

func secretsEqual(expected, given string) bool {
	if expected == "" || given == "" {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(expected), []byte(given)) == 1
}

func parseAzureEvents(b []byte) ([]azureEvent, error) {
	var raws []json.RawMessage
	if err := json.Unmarshal(b, &raws); err != nil {
		return nil, fmt.Errorf("error unmarshalling azure notification array: %v", err)
	}

	events := make([]azureEvent, 0, len(raws))
	for _, raw := range raws {
		ev := azureEvent{RawData: raw}

View on GitHub (pinned to 670c01717d)

Solutions

  1. Re-register the Event Grid subscription with the secret appended, e.g. https://host/webhooks/azure?secret=<sharedSecret>, or configure the matching header on delivery.
  2. Verify the configured shared secret in the app matches the one embedded in the Event Grid subscription URL/header; rotate both together.
  3. Check that any reverse proxy or WAF in front of the app forwards the query parameter and custom header intact.
  4. If this error appears from unknown scanners, it is working as intended — consider rate-limiting/blocking the source IP.

Example fix

// before
az bg create --topic ... --endpoint https://host/webhooks/azure
// after
az bg create --topic ... --endpoint "https://host/webhooks/azure?secret=YOUR_SHARED_SECRET"
Defensive patterns

Strategy: try-catch

Validate before calling

u, _ := url.Parse(webhookURL)
if u.Query().Get("secret") == "" {
	// subscription URL lacks the shared secret; fix before registering with Event Grid
}

Type guard

func azureWebhookURLHasSecret(raw string) bool {
	u, err := url.Parse(raw)
	return err == nil && strings.TrimSpace(u.Query().Get("secret")) != ""
}

Try / catch

bounces, err := azure.ProcessBounce(body, req)
if err != nil {
	if strings.Contains(err.Error(), "invalid azure event grid shared secret") {
		// return 401; alert if the source IP is not an Azure datacenter
		return echo.NewHTTPError(http.StatusUnauthorized)
	}
	return err
}

Prevention

When it happens

Trigger: A POST arrives at the Azure webhook while a shared secret is configured, but the request carries no secret or a wrong secret in both the expected query parameter and header.

Common situations: The Azure Event Grid subscription was created without appending the secret to the webhook URL query string; the secret was rotated in the app config but the subscription still uses the old URL; a proxy strips custom headers or the query string; the endpoint is being probed by unauthorized traffic.

Related errors


AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01). Data as JSON: /api/errors/7cf3d1972254b5b0. Report an issue: GitHub.