{"record":{"id":"f86ea8e2aebaad3d","repo":"knadh/listmonk","slug":"empty-event-payload","errorCode":null,"errorMessage":"empty event payload","messagePattern":"empty event payload","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/bounce/webhooks/azure.go","lineNumber":61,"sourceCode":"}\n\n// NewAzure returns a new Azure webhook handler.\nfunc NewAzure(sharedSecret, sharedSecretHeader string) *Azure {\n\treturn &Azure{\n\t\tsharedSecret:       strings.TrimSpace(sharedSecret),\n\t\tsharedSecretHeader: strings.TrimSpace(sharedSecretHeader),\n\t}\n}\n\n// ProcessSubscription processes Event Grid subscription validation requests and\n// returns the response payload that should be written to HTTP response body.\nfunc (a *Azure) ProcessSubscription(b []byte) (json.RawMessage, error) {\n\tevents, err := parseAzureEvents(b)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(events) == 0 {\n\t\treturn nil, errors.New(\"empty event payload\")\n\t}\n\n\t// Validation code arrives in the first event for subscription validation flow.\n\tvar payload map[string]json.RawMessage\n\tif err := json.Unmarshal(events[0].RawData, &payload); err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading validation payload: %v\", err)\n\t}\n\n\trawData, ok := payload[\"data\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"missing event data\")\n\t}\n\n\tvar data map[string]string\n\tif err := json.Unmarshal(rawData, &data); err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading validation data: %v\", err)\n\t}\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/bounce/webhooks/azure.go#L43-L79","documentation":"ProcessSubscription handles Azure Event Grid subscription validation webhooks. This error is returned when, after successfully parsing the webhook body, the payload contains zero events — there is nothing to validate or process. The library throws it as a guard against empty request bodies so it never indexes into a non-existent first event.","triggerScenarios":"An HTTP POST reaches the Azure bounce webhook endpoint whose body parses (via parseAzureEvents) into an empty event array — e.g. an empty JSON array [], an empty body that parses cleanly, or a health-check/probe request hitting the webhook URL.","commonSituations":"Load balancer or uptime probes POSTing empty bodies to the webhook endpoint; a misconfigured Azure Event Grid subscription delivering an empty batch; testing the endpoint with curl and no body.","solutions":["Send a real Azure Event Grid subscription validation event (with eventType SubscriptionValidationEvent and a validationCode) when registering the webhook.","Return 400 without retrying if events are empty — the request is simply not an Event Grid delivery.","Check that the Event Grid subscription points at the correct webhook endpoint.","Add a client-side emptiness check before posting if you are driving ProcessSubscription programmatically."],"exampleFix":"// before\nres, err := azure.ProcessSubscription(body)\n// after\nif len(body) == 0 {\n\treturn echo.NewHTTPError(http.StatusBadRequest, \"empty body\")\n}\nres, err := azure.ProcessSubscription(body)","handlingStrategy":"validation","validationCode":"if len(body) == 0 || strings.TrimSpace(string(body)) == \"\" || string(body) == \"[]\" {\n\treturn echo.NewHTTPError(http.StatusBadRequest, \"empty event payload\")\n}","typeGuard":"func hasAzureEvents(b []byte) bool {\n\tvar evts []struct{ RawData json.RawMessage `json:\"data\"` }\n\treturn json.Unmarshal(b, &evts) == nil && len(evts) > 0\n}","tryCatchPattern":"res, err := azure.ProcessSubscription(body)\nif err != nil {\n\tif strings.Contains(err.Error(), \"empty event payload\") {\n\t\treturn echo.NewHTTPError(http.StatusBadRequest, \"no events\") // do not retry\n\t}\n\treturn err\n}","preventionTips":["Configure uptime probes to use a dedicated health endpoint, not the webhook URL.","Validate Event Grid subscription registration sends a real validation event.","Log the raw body (truncated) for requests rejected as empty.","Reject empty bodies at the HTTP layer before invoking ProcessSubscription."],"tags":["azure","event-grid","webhook","validation"],"backgroundTag":"empty-webhook-payload","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}