knadh/listmonk · error
error unmarshalling azure notification array: %v
Error message
error unmarshalling azure notification array: %v
What it means
parseAzureEvents first unmarshals the entire request body as a JSON array of raw events. This error is thrown when the body is not a JSON array at all — Event Grid always posts an array, so a non-array body indicates malformed or unexpected input to either ProcessSubscription or ProcessBounce.
Source
Thrown at internal/bounce/webhooks/azure.go:183
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}
if err := json.Unmarshal(raw, &ev); err != nil {
return nil, fmt.Errorf("error unmarshalling azure event: %v", err)
}
events = append(events, ev)
}
return events, nil
}
func mapAzureStatus(status, details string) (string, bool) {
s := strings.ToLower(strings.TrimSpace(status))
d := strings.ToLower(strings.TrimSpace(details))
View on GitHub (pinned to 670c01717d)
Solutions
- Wrap the payload in a JSON array before sending: Event Grid expects `[{...}, {...}]`.
- Verify the webhook route only accepts POST with Content-Type application/json and log the raw body on failure.
- Check that any middleware (gzip decompression, body logging, re-signing) is not consuming or corrupting req.Body before it reaches the handler.
- Test the endpoint with a captured real Event Grid payload to confirm parsing works.
Example fix
// before: single object body
body := []byte(`{"eventType":"Microsoft.EventGrid.SubscriptionValidationEvent","data":{"validationCode":"x"}}`)
// after: array body as Event Grid sends
body := []byte(`[{"eventType":"Microsoft.EventGrid.SubscriptionValidationEvent","data":{"validationCode":"x"}}]`) Defensive patterns
Strategy: validation
Validate before calling
func isJSONEventArray(body []byte) bool {
var raws []json.RawMessage
return len(body) > 0 && json.Unmarshal(body, &raws) == nil && len(raws) > 0
} Type guard
func isArrayBody(body []byte) bool {
var arr []any
return json.Unmarshal(body, &arr) == nil
} Try / catch
bounces, err := azure.ProcessBounce(req, body)
if err != nil {
if strings.Contains(err.Error(), "unmarshalling azure notification array") {
log.Printf("malformed Event Grid body: %v; raw=%q", err, string(body))
http.Error(w, "malformed payload", http.StatusBadRequest)
return
}
http.Error(w, "webhook processing failed", http.StatusInternalServerError)
} Prevention
- Reject non-POST or non-application/json requests at the route level before reading the body.
- Log the raw body whenever webhook parsing fails so you can diff against a real Event Grid envelope.
- Add an integration test using an actual captured Event Grid delivery payload.
- Ensure gzip/charset middleware is not corrupting the request body.
When it happens
Trigger: Calling ProcessSubscription or ProcessBounce with: an empty body, a single JSON object not wrapped in an array (e.g. `{"eventType":...}` instead of `[{...}]`), truncated/invalid JSON, HTML from a misrouted request, or form-encoded data.
Common situations: Testing the webhook with curl and forgetting the surrounding brackets; reverse proxy stripping or corrupting the body; posting a single event object from a local Azure emulator or hand-rolled test; wrong HTTP method/route hitting the handler (e.g. GET health check body); charset/gzip issues leaving the body unreadable.
Related errors
- error reading validation data: %v
- error unmarshalling azure event: %v
- empty event payload
- missing event data
- missing validationCode in subscription payload
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/61495ee83385cfb4.
Report an issue: GitHub.