{"record":{"id":"c168b88ac6a169ac","repo":"Significant-Gravitas/AutoGPT","slug":"invalid-payload","errorCode":null,"errorMessage":"Invalid payload","messagePattern":"Invalid payload","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"autogpt_platform/backend/backend/api/features/v1.py","lineNumber":1437,"sourceCode":"    if not webhook_secret:\n        # Guard: an empty secret allows HMAC forgery (attacker can compute a valid\n        # signature over the same empty key). Reject all webhook calls when unconfigured.\n        logger.error(\n            \"stripe_webhook: STRIPE_WEBHOOK_SECRET is not configured — \"\n            \"rejecting request to prevent signature bypass\"\n        )\n        raise HTTPException(status_code=503, detail=\"Webhook not configured\")\n\n    # Get the raw request body\n    payload = await request.body()\n    # Get the signature header\n    sig_header = request.headers.get(\"stripe-signature\")\n\n    try:\n        event = stripe.Webhook.construct_event(payload, sig_header, webhook_secret)\n    except ValueError:\n        # Invalid payload\n        raise HTTPException(status_code=400, detail=\"Invalid payload\")\n    except stripe.SignatureVerificationError:\n        # Invalid signature\n        raise HTTPException(status_code=400, detail=\"Invalid signature\")\n\n    # Defensive payload extraction. A malformed payload (missing/non-dict\n    # `data.object`, missing `id`) would otherwise raise KeyError/TypeError\n    # AFTER signature verification — which Stripe interprets as a delivery\n    # failure and retries forever, while spamming Sentry with no useful info.\n    # Acknowledge with 200 and a warning so Stripe stops retrying.\n    event_id = event.get(\"id\", \"\")\n    event_type = event.get(\"type\", \"\")\n\n    # Event-level dedup: short-circuit identical re-deliveries before any\n    # handler runs. Stripe retries the same event.id on non-2xx responses, and\n    # not every downstream handler is independently idempotent.\n    if not await _claim_stripe_event(event_id):\n        logger.info(\n            \"stripe_webhook: event %s (%s) already processed; skipping\",","sourceCodeStart":1419,"sourceCodeEnd":1455,"githubUrl":"https://github.com/Significant-Gravitas/AutoGPT/blob/9c8bb5550f446ba5d3046b78896578742495b3cf/autogpt_platform/backend/backend/api/features/v1.py#L1419-L1455","documentation":"Raised (400) by /credits/stripe_webhook when stripe.Webhook.construct_event raises ValueError, which the Stripe SDK uses for a malformed request body (payload is not valid JSON / cannot be parsed as an event). It fires before signature verification semantics matter — the body itself is unparseable.","triggerScenarios":"Any non-Stripe client POSTing garbage (HTML error page, form-encoded data, truncated JSON) to /credits/stripe_webhook; a proxy or middleware that consumes and re-encodes the raw body incorrectly; curl tests without a proper JSON event body.","commonSituations":"Load balancer health probes hitting the webhook path; security scanners posting junk; middleware that reads request.body() before the handler and replaces it with a re-serialized object, breaking the exact bytes Stripe signed.","solutions":["Confirm the caller is really Stripe: webhooks must be raw POSTs of Stripe event JSON with a stripe-signature header.","Remove any middleware that buffers/re-encodes the request body; the handler needs the untouched raw bytes.","Reproduce with `stripe trigger <event>` or the Stripe CLI to verify genuine deliveries pass."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if (request.headers.get('content-type') !== 'application/json') reject(400); // only at your own edge; genuine Stripe deliveries are raw JSON POSTs","typeGuard":null,"tryCatchPattern":"Stripe retries 400s for up to ~3 days; a persistent 400 pattern means a proxy or non-Stripe caller is hitting the endpoint — fix the source, don't catch.","preventionTips":["Keep /credits/stripe_webhook out of health-check/scanner paths; restrict it at the ingress to Stripe IP ranges if possible.","Never wrap the handler with body-parsing middleware — construct_event needs the exact raw bytes.","Verify webhook endpoint plumbing with `stripe trigger checkout.session.completed` after deploy."],"tags":["payments","stripe","webhook","http-400","payload"],"backgroundTag":null,"analyzedSha":"9c8bb5550f446ba5d3046b78896578742495b3cf","analyzedAt":"2026-08-14T17:17:21.957Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}