{"record":{"id":"35f1c889e7f6fac8","repo":"knadh/listmonk","slug":"missing-azure-event-grid-request-context","errorCode":null,"errorMessage":"missing azure event grid request context","messagePattern":"missing azure event grid request context","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/bounce/webhooks/azure.go","lineNumber":153,"sourceCode":"\t\t})\n\t}\n\n\treturn out, nil\n}\n\nfunc (a *Azure) verifyAuth(req *http.Request) error {\n\tconst (\n\t\tdefaultSecretHeader = \"X-Listmonk-Webhook-Secret\"\n\t\tquerySecretParam    = \"code\"\n\t)\n\n\t// If no local credential is configured, allow webhook payloads.\n\tif a.sharedSecret == \"\" {\n\t\treturn nil\n\t}\n\n\tif req == nil {\n\t\treturn errors.New(\"missing azure event grid request context\")\n\t}\n\n\tquerySecret := strings.TrimSpace(req.URL.Query().Get(querySecretParam))\n\tif secretsEqual(a.sharedSecret, querySecret) {\n\t\treturn nil\n\t}\n\n\theaderName := a.sharedSecretHeader\n\tif headerName == \"\" {\n\t\theaderName = defaultSecretHeader\n\t}\n\theaderSecret := strings.TrimSpace(req.Header.Get(headerName))\n\tif secretsEqual(a.sharedSecret, headerSecret) {\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"invalid azure event grid shared secret\")\n}","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/bounce/webhooks/azure.go#L135-L171","documentation":"verifyAuth, called by ProcessBounce, authenticates Azure Event Grid deliveries only when a shared secret is configured. This error is returned when a shared secret IS configured but the function receives a nil *http.Request, meaning there is no request context (query param or header) against which to verify the secret. It protects the strict-auth path from silently passing or panicking.","triggerScenarios":"The Azure webhook is constructed with a shared secret, but ProcessBounce is invoked (e.g. programmatically or from a queue worker replaying stored payloads) without passing the original *http.Request, so verifyAuth gets nil.","commonSituations":"Replaying stored webhook bodies in tests or a redelivery worker without the original request; refactoring changed the call site to drop the request parameter; unit tests calling ProcessBounce directly while the deployment has a shared secret configured.","solutions":["Pass the original inbound *http.Request to ProcessBounce so verifyAuth can read the secret from the query param or header.","If invoking outside HTTP (queue replay), fetch the stored secret value and provide a reconstructed request, or temporarily run that path without a configured shared secret.","In tests, either set no shared secret (NewAzure with empty secret) or pass an httptest.Request with the secret attached.","Review call sites of ProcessBounce to ensure none omit the request after the auth feature was added."],"exampleFix":"// before\nbounces, err := azure.ProcessBounce(body, nil)\n// after\nbounces, err := azure.ProcessBounce(body, c.Request())","handlingStrategy":"try-catch","validationCode":"if req == nil && os.Getenv(\"AZURE_WEBHOOK_SECRET\") != \"\" {\n\t// shared secret configured but no request context: refuse before calling ProcessBounce\n}","typeGuard":"func canVerifyAzure(azure *webhooks.Azure, req *http.Request) bool {\n\treturn req != nil // or reflect on configured secret if exposed\n}","tryCatchPattern":"bounces, err := azure.ProcessBounce(body, req)\nif err != nil {\n\tif strings.Contains(err.Error(), \"missing azure event grid request context\") {\n\t\tlog.Printf(\"azure webhook called without request context; check call site: %v\", err)\n\t}\n\treturn err\n}","preventionTips":["Always pass the inbound *http.Request into ProcessBounce in HTTP handlers (c.Request()).","In queue-replay workers, reconstruct an *http.Request carrying the stored secret.","In unit tests, configure the webhook without a shared secret or use httptest requests.","Add a startup/CI assertion that no call site passes nil where a secret is configured."],"tags":["azure","event-grid","webhook","auth"],"backgroundTag":"missing-request-context","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}