knadh/listmonk · error

error unmarshalling azure event: %v

Error message

error unmarshalling azure event: %v

What it means

After the body parses as a JSON array, parseAzureEvents unmarshals each element into the azureEvent struct (eventType + data). This error is thrown when an individual array element is not a valid JSON object or contains fields whose types conflict with the struct (e.g. `data` is a string instead of an object).

Source

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

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))

	switch s {
	case "bounced", "suppressed":
		return models.BounceTypeHard, true
	case "failed":
		// Infer severity from SMTP-enhanced status if available.
		if m := reSMTPStatus.FindStringSubmatch(d); len(m) > 1 {
			if strings.HasPrefix(m[1], "5.") {

View on GitHub (pinned to 670c01717d)

Solutions

  1. Confirm the events posted are Azure Communication Services email events where `data` is a JSON object with string fields.
  2. Validate each array element is a JSON object before posting (or in tests, use a captured real Event Grid envelope).
  3. Log the raw body and the wrapped json.Unmarshal error to identify which element/field conflicts.
  4. If testing, use realistic payload samples rather than placeholder strings in the array.

Example fix

// before: array of strings
body := []byte(`["event1","event2"]`)

// after: array of event objects
body := []byte(`[{"eventType":"Microsoft.Communication.EmailDeliveryReportReceived","data":{"recipient":"a@b.com","status":"Bounced"}}]`)
Defensive patterns

Strategy: validation

Validate before calling

func hasWellFormedEvents(body []byte) bool {
    var evs []struct {
        EventType string          `json:"eventType"`
        Data      json.RawMessage `json:"data"`
    }
    if json.Unmarshal(body, &evs) != nil {
        return false
    }
    for _, e := range evs {
        if e.EventType == "" || !json.Valid(e.Data) {
            return false
        }
        var obj map[string]any
        if json.Unmarshal(e.Data, &obj) != nil {
            return false
        }
    }
    return len(evs) > 0
}

Type guard

func isEventObject(raw json.RawMessage) bool {
    var ev struct {
        EventType string          `json:"eventType"`
        Data      json.RawMessage `json:"data"`
    }
    if json.Unmarshal(raw, &ev) != nil || ev.EventType == "" {
        return false
    }
    var obj map[string]any
    return json.Unmarshal(ev.Data, &obj) == nil
}

Try / catch

bounces, err := azure.ProcessBounce(req, body)
if err != nil {
    if strings.Contains(err.Error(), "unmarshalling azure event") {
        log.Printf("bad event element in Event Grid batch: %v", err)
        http.Error(w, "invalid event format", http.StatusBadRequest)
        return
    }
    http.Error(w, "webhook processing failed", http.StatusInternalServerError)
}

Prevention

When it happens

Trigger: Calling ProcessSubscription or ProcessBounce with an array containing: raw strings/numbers instead of event objects, an event whose `eventType` is not a string, an event whose `data` is not a JSON object, or corrupt JSON in one element of a batch.

Common situations: Event Grid pushing a different subscription kind (e.g. storage/blob events with differently typed data) to the same endpoint; manual tests with placeholder arrays like `["test"]`; a provider schema update changing field types; binary or truncated bodies from proxy misconfiguration.

Related errors


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