{"record":{"id":"4876e74bc808ebb9","repo":"Significant-Gravitas/AutoGPT","slug":"invalid-signature","errorCode":null,"errorMessage":"Invalid signature","messagePattern":"Invalid signature","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"autogpt_platform/backend/backend/api/features/v1.py","lineNumber":1440,"sourceCode":"        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\",\n            event_id,\n            event_type,\n        )","sourceCodeStart":1422,"sourceCodeEnd":1458,"githubUrl":"https://github.com/Significant-Gravitas/AutoGPT/blob/9c8bb5550f446ba5d3046b78896578742495b3cf/autogpt_platform/backend/backend/api/features/v1.py#L1422-L1458","documentation":"Raised (400) by /credits/stripe_webhook when stripe.Webhook.construct_event raises SignatureVerificationError: the payload parsed fine, but the stripe-signature header does not contain a valid HMAC-SHA256 signature for the body under the configured webhook secret. This is how Stripe's SDK reports forged or mis-keyed deliveries.","triggerScenarios":"A request with parseable JSON but a missing/incorrect stripe-signature header: attackers replaying modified events, a legitimate Stripe endpoint whose signing secret differs from STRIPE_WEBHOOK_SECRET on this server (e.g. test-mode events hitting a prod secret), or body bytes altered in transit by a proxy.","commonSituations":"Multiple webhook endpoints in Stripe sharing one URL but different secrets; secret rotated in Stripe but not in the app; TLS-terminating proxy that rewrites the body (e.g. compression/encoding changes); local dev pointing the Stripe CLI at an env with another env's secret.","solutions":["Verify STRIPE_WEBHOOK_SECRET matches the signing secret of the exact Stripe webhook endpoint delivering the event (test and live secrets differ).","Ensure nothing between Stripe and the app mutates the body — disable body-modifying transforms on the ingress.","If testing locally, send events via `stripe listen --forward-to ...` and use the CLI's printed secret."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# confirm the secret matches the endpoint before going live\nimport stripe, os\nep = stripe.WebhookEndpoint.list(limit=100)\nassert any(os.environ['STRIPE_WEBHOOK_SECRET'] for _ in [0])  # placeholder; real check: secret comes from the same endpoint config","typeGuard":null,"tryCatchPattern":"Persistent SignatureVerificationError on genuine Stripe deliveries = secret mismatch or body mutation; investigate ingress (compression/rewrite) and secret config — do not catch and ignore.","preventionTips":["One webhook endpoint URL <-> exactly one signing secret; never share a URL across endpoints with different secrets.","Disable request-body-modifying features (rewrites, encoding normalizations) on the webhook route.","Keep test-mode and live-mode secrets separated per environment."],"tags":["payments","stripe","webhook","security","http-400","signature"],"backgroundTag":null,"analyzedSha":"9c8bb5550f446ba5d3046b78896578742495b3cf","analyzedAt":"2026-08-14T17:17:21.957Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}