Skip to content

feat(stripe): Stripe subscription webhook trigger blocks#13435

Open
Torantulino wants to merge 1 commit into
devfrom
feat/stripe-webhook-blocks
Open

feat(stripe): Stripe subscription webhook trigger blocks#13435
Torantulino wants to merge 1 commit into
devfrom
feat/stripe-webhook-blocks

Conversation

@Torantulino

Copy link
Copy Markdown
Member

Summary

Adds Stripe as a first-class webhook provider so agents can fire on real subscription lifecycle events — sourced directly from Stripe rather than the internal DB (which includes team and demo accounts).

Context

From #key-metrics: the team wanted a notification every time someone gets/upgrades a subscription. Querying the DB was identified as the wrong approach because it counts internal + demo accounts. Stripe webhooks give us external customers only.

Changes

New provider

  • ProviderName.STRIPE = "stripe" in providers.py

Webhook manager (integrations/webhooks/stripe.py)

  • Auto-registers webhook endpoints in Stripe via API on block setup
  • Stores Stripe's signing secret (whsec_...) in webhook.config (returned once on creation)
  • Verifies Stripe-Signature header using timestamp + HMAC-SHA256 with 5-min replay tolerance
  • Deregisters endpoint cleanly on teardown

Blocks (blocks/stripe/)

  • _auth.py — API-key credentials (sk_live_... / sk_test_...)
  • triggers.pyStripeSubscriptionTriggerBlock
    • Event filter: created / updated / deleted (created + updated on by default)
    • Outputs: subscription_id, customer_id, status, plan_name, plan_interval, amount_cents, currency, livemode, full payload
  • example_payloads/ — realistic example payloads for unit tests

Usage

  1. Add a Stripe Subscription Trigger block to your agent
  2. Connect your Stripe API key (requires webhook endpoint write permission)
  3. The platform auto-registers a webhook endpoint in your Stripe account
  4. Wire the outputs (e.g. customer_id, plan_name) to a Discord notification block

Testing

  • All new files pass AST/syntax validation
  • Example payloads cover customer.subscription.created and customer.subscription.updated
  • test_input / test_output wired in the block __init__ for the existing test runner
Adds Stripe as a first-class webhook provider so agents can fire on
real subscription events (created / updated / deleted) — pulling
directly from Stripe instead of the internal DB, which includes team
and demo accounts.

Changes:
- providers.py: add ProviderName.STRIPE = "stripe"
- integrations/webhooks/stripe.py: StripeWebhooksManager
    - auto-registers webhook endpoints via Stripe API on block setup
    - stores Stripe's signing secret (whsec_...) in webhook.config
    - verifies Stripe-Signature header (timestamp + HMAC-SHA256)
    - deregisters endpoint on teardown
- integrations/webhooks/__init__.py: register StripeWebhooksManager
- blocks/stripe/_auth.py: API-key credentials helper
- blocks/stripe/triggers.py: StripeSubscriptionTriggerBlock
    - event filter: created / updated / deleted
    - outputs: subscription_id, customer_id, status, plan_name,
      plan_interval, amount_cents, currency, livemode
- blocks/stripe/example_payloads/: example webhook payloads for tests

Resolves: #key-metrics request for subscription change notifications
without DB-level noise from internal accounts.
@Torantulino Torantulino requested a review from a team as a code owner June 26, 2026 18:18
@Torantulino Torantulino requested review from 0ubbe and ntindle and removed request for a team June 26, 2026 18:18
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Jun 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR targets the master branch but does not come from dev or a hotfix/* branch.

Automatically setting the base branch to dev.

@github-actions github-actions Bot changed the base branch from master to dev June 26, 2026 18:18
@github-actions github-actions Bot added platform/backend AutoGPT Platform - Back end platform/blocks labels Jun 26, 2026
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds Stripe provider support across credentials, webhook management, and subscription trigger handling, plus example payload fixtures for created and updated subscription events.

Changes

Stripe support

Layer / File(s) Summary
Provider and credentials
autogpt_platform/backend/backend/integrations/providers.py, autogpt_platform/backend/backend/blocks/stripe/_auth.py
Adds ProviderName.STRIPE, Stripe credential aliases, the Stripe credentials field helper, and test credential constants.
Webhook manager
autogpt_platform/backend/backend/integrations/webhooks/stripe.py, autogpt_platform/backend/backend/integrations/webhooks/__init__.py
Adds Stripe webhook signature verification, payload validation, endpoint registration/deregistration, and webhook manager registration.
Subscription trigger block and fixtures
autogpt_platform/backend/backend/blocks/stripe/triggers.py, autogpt_platform/backend/backend/blocks/stripe/example_payloads/*.json
Adds the Stripe subscription trigger block with input/output schemas, example payload loading, subscription parsing, and event fixtures.

Sequence Diagram(s)

sequenceDiagram
  participant StripeWebhooksManager
  participant Stripe API
  participant WebhookConfig
  StripeWebhooksManager->>Stripe API: POST /webhook_endpoints
  Stripe API-->>StripeWebhooksManager: endpoint id and signing_secret
  StripeWebhooksManager->>WebhookConfig: store endpoint id and signing_secret
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • majdyz
  • ntindle

Poem

A rabbit hops through Stripe-lit rain,
With webhook ears and secret gain.
New subscription moons now twinkle bright,
I sniff the payload, hop with delight.
🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding Stripe subscription webhook trigger blocks.
Description check ✅ Passed The description is clearly related and accurately covers the Stripe webhook provider, trigger block, and example payload changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/stripe-webhook-blocks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

🔍 PR Overlap Detection

This check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early.

🟢 Low Risk — File Overlap Only

These PRs touch the same files but different sections (click to expand)

Summary: 0 conflict(s), 0 medium risk, 2 low risk (out of 2 PRs with file overlap)


Auto-generated on push. Ignores: openapi.json, lock files.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
autogpt_platform/backend/backend/blocks/stripe/triggers.py (1)

85-87: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move StripeWebhookType to the top-level imports.

This lazy import isn't guarding a heavy optional dependency, so it should follow the normal backend import pattern. As per coding guidelines, "Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies like openpyxl".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autogpt_platform/backend/backend/blocks/stripe/triggers.py` around lines 85 -
87, Move the StripeWebhookType import out of the __init__ method and into the
module’s top-level imports in triggers.py. This is not a lazy import for a heavy
optional dependency, so update the Stripe trigger block to follow the normal
backend import pattern and reference StripeWebhookType directly where it is
used.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@autogpt_platform/backend/backend/blocks/stripe/_auth.py`:
- Around line 19-23: The Stripe credentials field in StripeCredentialsField is
missing the runtime metadata needed to restrict the picker to Stripe API-key
credentials. Update the CredentialsField call to include the Stripe provider and
supported_credential_types for API key credentials so only the correct
credential type is surfaced. Keep the existing description, and ensure the
returned StripeCredentialsInput remains unchanged while the runtime metadata is
added where StripeCredentialsField builds the field.

In `@autogpt_platform/backend/backend/blocks/stripe/triggers.py`:
- Around line 81-83: Rename the declared output field in the Stripe trigger
block from the reserved error channel to a non-reserved name, since returning
("error", ...) from _execute() raises BlockExecutionError instead of flowing
downstream. Update the field definition in the trigger schema and any matching
payload/yield logic in the Stripe trigger implementation so parse failures are
emitted as a normal output (for example parse_error) or allowed to propagate as
exceptions instead of using the reserved error key.
- Around line 47-50: The EventsFilter-backed events field in the Stripe trigger
schema is still required because SchemaField has no real default object. Update
the events definition in the trigger block so it supplies an actual default
instance of EventsFilter (with created and updated enabled) rather than relying
only on nested defaults, keeping the field optional by default.

In `@autogpt_platform/backend/backend/integrations/webhooks/stripe.py`:
- Around line 50-73: The Stripe webhook verification in the signature-parsing
logic only keeps one v1 value, so update the parsing in the webhook handler to
collect every v1 signature from the Stripe-Signature header instead of using a
dict comprehension that overwrites duplicates. In the same verification flow
that builds signed_payload and compares against expected, loop through all
parsed v1 signatures and accept the request if any one matches the computed HMAC
for the active signing secret(s), preserving the timestamp checks and existing
HTTPException behavior for invalid headers.
- Around line 85-91: `validate_payload()` in the Stripe webhook flow should
handle malformed JSON before reading `payload["type"]`; wrap `await
request.json()` with explicit catching of JSON decode errors such as
`orjson.JSONDecodeError`/`json.JSONDecodeError` and raise
`HTTPException(status_code=400, ...)` instead of letting the exception bubble.
Keep the existing missing-event-type 400 behavior unchanged, and make sure the
fix is localized to `validate_payload()` in the Stripe webhook integration.

---

Nitpick comments:
In `@autogpt_platform/backend/backend/blocks/stripe/triggers.py`:
- Around line 85-87: Move the StripeWebhookType import out of the __init__
method and into the module’s top-level imports in triggers.py. This is not a
lazy import for a heavy optional dependency, so update the Stripe trigger block
to follow the normal backend import pattern and reference StripeWebhookType
directly where it is used.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e7585315-6920-4bef-8a91-fdbb7173224f

📥 Commits

Reviewing files that changed from the base of the PR and between e2711b1 and 7c97a4c.

📒 Files selected for processing (8)
  • autogpt_platform/backend/backend/blocks/stripe/__init__.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.created.json
  • autogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.updated.json
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📜 Review details
⏰ Context from checks skipped due to timeout. (16)
  • GitHub Check: check API types
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (python)
  • GitHub Check: end-to-end tests
  • GitHub Check: type-check (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: type-check (3.11)
  • GitHub Check: type-check (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: lint
  • GitHub Check: test (3.13)
  • GitHub Check: types
  • GitHub Check: check-docs-sync
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (typescript)
  • GitHub Check: Check PR Status
🧰 Additional context used
📓 Path-based instructions (4)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

autogpt_platform/backend/**/*.py: Use poetry run ... command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies like openpyxl
Use absolute imports with from backend.module import ... for cross-package imports; single-dot relative imports are acceptable for sibling modules within the same package; avoid double-dot relative imports
Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead
Use Pydantic models over dataclass/namedtuple/dict for structured data
Do not use linter suppressors — no # type: ignore, # noqa, # pyright: ignore; fix the type/code instead
Prefer list comprehensions over manual loop-and-append patterns
Use early return with guard clauses first to avoid deep nesting
Use %s for deferred interpolation in debug log statements for efficiency; use f-strings elsewhere for readability (e.g., logger.debug("Processing %s items", count) vs logger.info(f"Processing {count} items"))
Sanitize error paths by using os.path.basename() in error messages to avoid leaking directory structure
Be aware of TOCTOU (Time-Of-Check-Time-Of-Use) issues — avoid check-then-act patterns for file access and credit charging
Use transaction=True for Redis pipelines to ensure atomicity on multi-step operations
Use max(0, value) guards for computed values that should never be negative
Keep files under ~300 lines; if a file grows beyond this, split by responsibility (extract helpers, models, or a sub-module into a new file)
Keep functions under ~40 lines; extract named helpers when a function grows longer
...

Files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
autogpt_platform/{backend,autogpt_libs}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
autogpt_platform/backend/**/*.{json,yaml,yml,toml,config}

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Include agent configuration in dedicated configuration files

Files:

  • autogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.created.json
  • autogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.updated.json
autogpt_platform/backend/backend/blocks/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/backend/blocks/**/*.py: Inherit from 'Block' base class with input/output schemas when adding new blocks in backend
Implement 'run' method with proper error handling in backend blocks
Generate block UUID using 'uuid.uuid4()' when creating new blocks in backend
Write tests alongside block implementation when adding new blocks in backend

autogpt_platform/backend/backend/blocks/**/*.py: For blocks handling files, use store_media_file() with return_format="for_local_processing" when processing with local tools (ffmpeg, MoviePy, PIL)
For blocks handling files, use store_media_file() with return_format="for_external_api" when sending content to external APIs (Replicate, OpenAI)
For blocks returning files, use store_media_file() with return_format="for_block_output" to enable auto-adaptation to execution context (workspace:// in CoPilot, data URI in graphs)
When creating new blocks, inherit from Block base class, define input/output schemas using BlockSchema, implement async run method, and generate unique block ID using uuid.uuid4()

Files:

  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
🧠 Learnings (17)
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.created.json
  • autogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.updated.json
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📚 Learning: 2026-03-16T16:35:40.236Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/api/features/workflow_import.py:54-63
Timestamp: 2026-03-16T16:35:40.236Z
Learning: Avoid using the word 'competitor' in public-facing identifiers and text. Use neutral naming for API paths, model names, function names, and UI text. Examples: rename 'CompetitorFormat' to 'SourcePlatform', 'convert_competitor_workflow' to 'convert_workflow', '/competitor-workflow' to '/workflow'. Apply this guideline to files under autogpt_platform/backend and autogpt_platform/frontend.

Applied to files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📚 Learning: 2026-03-31T15:37:38.626Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/tools/agent_generator/fixer.py:37-47
Timestamp: 2026-03-31T15:37:38.626Z
Learning: When validating/constructing Anthropic API model IDs in Significant-Gravitas/AutoGPT, allow the hyphen-separated Claude Opus 4.6 model ID `claude-opus-4-6` (it corresponds to `LlmModel.CLAUDE_4_6_OPUS` in `autogpt_platform/backend/backend/blocks/llm.py`). Do NOT require the dot-separated form in Anthropic contexts. Only OpenRouter routing variants should use the dot separator (e.g., `anthropic/claude-opus-4.6`); `claude-opus-4-6` should be treated as correct when passed to Anthropic, and flagged only if it’s used in the OpenRouter path where the dot form is expected.

Applied to files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📚 Learning: 2026-04-15T02:43:36.890Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12780
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-15T02:43:36.890Z
Learning: When reviewing Python exception handlers, do not flag `isinstance(e, X)` checks as dead/unreachable if the caught exception `X` is a subclass of the exception type being handled. For example, if `X` (e.g., `VirusScanError`) inherits from `ValueError` (directly or via an intermediate class) and it can be raised within an `except ValueError:` block, then `isinstance(e, X)` inside that handler is reachable and should not be treated as dead code.

Applied to files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📚 Learning: 2026-05-23T05:29:43.085Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 13200
File: autogpt_platform/backend/backend/executor/scheduler.py:590-593
Timestamp: 2026-05-23T05:29:43.085Z
Learning: When reviewing Python code that uses Pydantic discriminated/tagged unions (e.g., `Annotated[Union[...], Field(discriminator="kind")]`), recognize that using `isinstance(x, SomeVariantInfo)` to narrow the union is an intentional and correct runtime guard and should also enable static type narrowing in tools like Pyright. Do not recommend replacing such `isinstance`-based narrowing with `cast(...)` when the check already proves the variant at runtime.

Applied to files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📚 Learning: 2026-04-22T11:46:04.431Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/config.py:0-0
Timestamp: 2026-04-22T11:46:04.431Z
Learning: Do not flag the Claude Sonnet 4.6 model ID as incorrect when it uses the project’s established hyphenated convention: `anthropic/claude-sonnet-4-6`. This hyphen form is the intentional, production convention and should be treated as valid (including in files like llm.py, blocks tests, reasoning.py, `_is_anthropic_model` tests, and config defaults). Note that OpenRouter also accepts the dot variant `anthropic/claude-sonnet-4.6`, so either form may be tolerated, but `anthropic/claude-sonnet-4-6` should be considered the standard to match project usage.

Applied to files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📚 Learning: 2026-04-22T11:46:12.892Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/baseline/service.py:322-332
Timestamp: 2026-04-22T11:46:12.892Z
Learning: In this codebase (Significant-Gravitas/AutoGPT), OpenRouter-routed Anthropic model IDs should use the hyphen-separated convention (e.g., `anthropic/claude-sonnet-4-6`, `anthropic/claude-opus-4-6`). Although OpenRouter may accept both hyphen and dot variants, treat the hyphen-separated form as the intended, correct codebase-wide convention and do not flag it as an error. Only flag the dot-separated variant (e.g., `anthropic/claude-sonnet-4.6`) as incorrect when reviewing/validating model ID strings for OpenRouter-routed Anthropic models.

Applied to files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📚 Learning: 2026-05-07T18:48:14.242Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 13040
File: autogpt_platform/backend/backend/blocks/llm.py:0-0
Timestamp: 2026-05-07T18:48:14.242Z
Learning: In this repository, isort may split imports from the same module into separate blocks when some imports are aliased (e.g., `from module import X as Y`) and others are not. Preserve the two-block layout when it results from isort (such as keeping `from openai.types.chat import ChatCompletion as OpenAIChatCompletion` separate from non-aliased imports from `openai.types.chat`). Do not treat that split as a style issue during review; merging them into a single block can fail CI with `Imports are incorrectly sorted and/or formatted`.

Applied to files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📚 Learning: 2026-05-26T14:24:34.866Z
Learnt from: Abhi1992002
Repo: Significant-Gravitas/AutoGPT PR: 13217
File: autogpt_platform/backend/backend/api/features/search/service.py:137-137
Timestamp: 2026-05-26T14:24:34.866Z
Learning: In the Significant-Gravitas/AutoGPT backend, treat `user_id` (an opaque UUID used only for correlation/tracing) as non-PII. Do not flag direct logging of `user_id` in `logger.warning`/`logger.info` statements as a PII exposure issue, as the established convention is to log `user_id` for tracing while reserving PII for fields like email or display name.

Applied to files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📚 Learning: 2026-06-11T19:39:10.493Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 13337
File: autogpt_platform/backend/backend/copilot/graphiti/reranker.py:0-0
Timestamp: 2026-06-11T19:39:10.493Z
Learning: In the Significant-Gravitas/AutoGPT Python backend, when calling the OpenAI Python client `chat.completions.create`, construct the `messages` payload using the concrete typed-dict variants from `openai.types.chat` (e.g., `ChatCompletionSystemMessageParam`, `ChatCompletionUserMessageParam`, etc.) rather than trying to instantiate `ChatCompletionMessageParam` directly. `ChatCompletionMessageParam` is a `Union` alias and is not constructible, so `ChatCompletionMessageParam(role=..., content=...)` should fail type checking. Build each message element with the appropriate concrete typed dict and then annotate the resulting list as `list[ChatCompletionMessageParam]` (e.g., `messages: list[ChatCompletionMessageParam] = [ChatCompletionSystemMessageParam(...), ...]`).

Applied to files:

  • autogpt_platform/backend/backend/integrations/webhooks/__init__.py
  • autogpt_platform/backend/backend/integrations/providers.py
  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
  • autogpt_platform/backend/backend/integrations/webhooks/stripe.py
📚 Learning: 2026-02-05T04:11:00.596Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11796
File: autogpt_platform/backend/backend/blocks/video/concat.py:3-4
Timestamp: 2026-02-05T04:11:00.596Z
Learning: In autogpt_platform/backend/backend/blocks/**/*.py, when creating a new block, generate a UUID once with uuid.uuid4() and hard-code the resulting string as the block's id parameter. Do not call uuid.uuid4() at runtime; IDs must be constant across all imports and runs to ensure stability.

Applied to files:

  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
📚 Learning: 2026-03-16T16:32:21.686Z
Learnt from: Abhi1992002
Repo: Significant-Gravitas/AutoGPT PR: 12417
File: autogpt_platform/backend/backend/blocks/agent_mail/pods.py:62-74
Timestamp: 2026-03-16T16:32:21.686Z
Learning: In autogpt_platform/backend/backend/blocks/, the Block base class execute() already wraps run() in a try/except to convert uncaught exceptions into BlockExecutionError/BlockUnknownError. Do not add per-block try/except in individual block run() methods, as this is not the established pattern (e.g., Gmail, Slack, Todoist blocks omit it). Only use explicit try/except within blocks that need to distinguish between success and error yield paths inside a generator (e.g., attachment blocks). This guidance applies to all Python files under autogpt_platform/backend/backend/blocks/ and similar block implementations; avoid duplicating error handling in run() unless a block requires generator-based branching.

Applied to files:

  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
📚 Learning: 2026-04-23T12:55:26.122Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12893
File: autogpt_platform/backend/backend/blocks/ayrshare/post_to_tiktok.py:24-24
Timestamp: 2026-04-23T12:55:26.122Z
Learning: Cost billing via the cost(*costs) decorator is applied at input-evaluation time (before a block’s run() executes). Therefore, mutating input_data inside run() will not change billing. When a block’s billing depends on a field plus URL/sniff-derived signals, treat the explicitly declared billing field (e.g., is_video) as the only billing source—set it correctly before run() (or in the code path that occurs before the decorator evaluates input_data). This should be checked for all blocks under autogpt_platform/backend/backend/blocks/ so billing signals are not mistakenly assumed to update during run().

Applied to files:

  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
📚 Learning: 2026-03-16T16:30:11.452Z
Learnt from: Abhi1992002
Repo: Significant-Gravitas/AutoGPT PR: 12417
File: autogpt_platform/backend/backend/blocks/agent_mail/threads.py:80-102
Timestamp: 2026-03-16T16:30:11.452Z
Learning: In autogpt_platform/backend/backend/blocks/ (and related blocks under autogpt_platform/backend/backend/blocks/), do not add try/except blocks around a block's run() method for standard error propagation. The block executor framework (backend/executor/manager.py) catches uncaught exceptions from run() and emits them on the 'error' output. Only add explicit try/except blocks when you need to control partial outputs in failure cases (e.g., certain outputs must not be yielded on error, as in attachment blocks). This is the standard pattern across the codebase; apply it broadly to blocks' run() implementations.

Applied to files:

  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
📚 Learning: 2026-03-16T16:30:23.196Z
Learnt from: Abhi1992002
Repo: Significant-Gravitas/AutoGPT PR: 12417
File: autogpt_platform/backend/backend/blocks/agent_mail/pods.py:62-74
Timestamp: 2026-03-16T16:30:23.196Z
Learning: In any Python file under autogpt_platform/backend/backend/blocks, do not add a try/except around run() solely for standard error handling. The block framework’s _execute() in _base.py already catches unhandled exceptions and re-raises as BlockExecutionError or BlockUnknownError. If you yield ("error", message), _execute() raises BlockExecutionError immediately, so the error port will not propagate downstream. Reserve explicit try/except for scenarios where you must control partial output (e.g., attachment blocks that must skip yielding content_base64 on failure).

Applied to files:

  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
📚 Learning: 2026-03-16T16:30:11.452Z
Learnt from: Abhi1992002
Repo: Significant-Gravitas/AutoGPT PR: 12417
File: autogpt_platform/backend/backend/blocks/agent_mail/threads.py:80-102
Timestamp: 2026-03-16T16:30:11.452Z
Learning: Do not wrap synchronous AgentMail SDK calls with asyncio.to_thread() in blocks under autogpt_platform/backend/backend/blocks (and across the codebase). The block executor runs node execution in dedicated threads via asyncio.run_coroutine_threadsafe (see manager.py around lines ~745-752 and ~1079). The existing pattern avoids using asyncio.to_thread for SDK calls inside async run() methods, so maintain that approach and do not add to_thread usage in these code paths.

Applied to files:

  • autogpt_platform/backend/backend/blocks/stripe/_auth.py
  • autogpt_platform/backend/backend/blocks/stripe/triggers.py
🔇 Additional comments (4)
autogpt_platform/backend/backend/integrations/providers.py (1)

49-49: LGTM!

autogpt_platform/backend/backend/integrations/webhooks/__init__.py (1)

18-28: LGTM!

autogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.created.json (1)

1-71: LGTM!

autogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.updated.json (1)

1-88: LGTM!

Comment on lines +19 to +23
def StripeCredentialsField() -> StripeCredentialsInput:
return CredentialsField(
description="Stripe API secret key (sk_live_... or sk_test_...). "
"Requires permission to create and delete webhook endpoints.",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Constrain this field to Stripe API-key credentials.

StripeCredentialsInput only helps static typing; it doesn't populate the runtime metadata that the credentials picker/webhook flow reads. Without provider and supported_credential_types, this block can surface unrelated credentials and _register_webhook() fails later on its APIKeyCredentials guard.

Suggested fix
 def StripeCredentialsField() -> StripeCredentialsInput:
     return CredentialsField(
+        provider=ProviderName.STRIPE,
+        supported_credential_types={"api_key"},
         description="Stripe API secret key (sk_live_... or sk_test_...). "
         "Requires permission to create and delete webhook endpoints.",
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def StripeCredentialsField() -> StripeCredentialsInput:
return CredentialsField(
description="Stripe API secret key (sk_live_... or sk_test_...). "
"Requires permission to create and delete webhook endpoints.",
)
def StripeCredentialsField() -> StripeCredentialsInput:
return CredentialsField(
provider=ProviderName.STRIPE,
supported_credential_types={"api_key"},
description="Stripe API secret key (sk_live_... or sk_test_...). "
"Requires permission to create and delete webhook endpoints.",
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autogpt_platform/backend/backend/blocks/stripe/_auth.py` around lines 19 -
23, The Stripe credentials field in StripeCredentialsField is missing the
runtime metadata needed to restrict the picker to Stripe API-key credentials.
Update the CredentialsField call to include the Stripe provider and
supported_credential_types for API key credentials so only the correct
credential type is surfaced. Keep the existing description, and ensure the
returned StripeCredentialsInput remains unchanged while the runtime metadata is
added where StripeCredentialsField builds the field.
Comment on lines +47 to +50
events: EventsFilter = SchemaField(
title="Events",
description="Subscription lifecycle events to subscribe to",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Give events a real default object.

The defaults inside EventsFilter don't make events optional. As written, the field is still required, so created/updated are not actually enabled by default.

Suggested fix
         events: EventsFilter = SchemaField(
             title="Events",
             description="Subscription lifecycle events to subscribe to",
+            default_factory=EventsFilter,
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
events: EventsFilter = SchemaField(
title="Events",
description="Subscription lifecycle events to subscribe to",
)
events: EventsFilter = SchemaField(
title="Events",
description="Subscription lifecycle events to subscribe to",
default_factory=EventsFilter,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autogpt_platform/backend/backend/blocks/stripe/triggers.py` around lines 47 -
50, The EventsFilter-backed events field in the Stripe trigger schema is still
required because SchemaField has no real default object. Update the events
definition in the trigger block so it supplies an actual default instance of
EventsFilter (with created and updated enabled) rather than relying only on
nested defaults, keeping the field optional by default.
Comment on lines +81 to +83
error: str = SchemaField(
description="Error message if the payload could not be parsed"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don't emit parse failures on the reserved error channel.

In this block framework, yielding ("error", ...) aborts execution via BlockExecutionError instead of producing a normal output, so this declared field won't reach downstream blocks. Rename it to something non-reserved (for example parse_error) or let the exception propagate. Based on learnings, yielding ("error", message) in backend blocks causes _execute() to raise immediately rather than propagate a normal output.

Also applies to: 135-161

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autogpt_platform/backend/backend/blocks/stripe/triggers.py` around lines 81 -
83, Rename the declared output field in the Stripe trigger block from the
reserved error channel to a non-reserved name, since returning ("error", ...)
from _execute() raises BlockExecutionError instead of flowing downstream. Update
the field definition in the trigger schema and any matching payload/yield logic
in the Stripe trigger implementation so parse failures are emitted as a normal
output (for example parse_error) or allowed to propagate as exceptions instead
of using the reserved error key.

Source: Learnings

Comment on lines +50 to +73
# Parse Stripe-Signature header: t=timestamp,v1=signature
parts = {k: v for k, v in (p.split("=", 1) for p in sig_header.split(","))}
timestamp = parts.get("t")
v1_sig = parts.get("v1")
if not timestamp or not v1_sig:
raise HTTPException(
status_code=403, detail="Invalid Stripe-Signature format"
)

# Reject stale timestamps
if abs(time.time() - int(timestamp)) > STRIPE_TIMESTAMP_TOLERANCE:
raise HTTPException(
status_code=403, detail="Stripe webhook timestamp is too old"
)

payload_body = await request.body()
signed_payload = f"{timestamp}.{payload_body.decode('utf-8')}"
expected = hmac.new(
signing_secret.encode("utf-8"),
msg=signed_payload.encode("utf-8"),
digestmod=hashlib.sha256,
).hexdigest()

if not hmac.compare_digest(expected, v1_sig):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Stripe webhook multiple signatures during secret rotation v1 header format

💡 Result:

During a Stripe webhook secret rotation, you may encounter multiple signatures in the Stripe-Signature header [1][2]. When you roll a secret via the Stripe Dashboard, you have the option to keep the previous secret active for up to 24 hours [1][3]. During this overlap window, Stripe generates a unique signature for each active secret, resulting in a header that contains multiple v1 signatures [1][2]. The Stripe-Signature header format is structured as a comma-separated string of key-value pairs [4][5]. It includes a timestamp (prefixed with t=) and one or more signature schemes (typically v1=) [1][6]. An example header might look like this [6][5]: t=1495999758,v1=ABC,v1=DEF When your server receives this, it should extract all values associated with the v1 scheme [5][7]. To verify the payload, your code must compute the HMAC-SHA256 signature using your stored secrets and compare it against each of the v1 signatures provided in the header [4][5][7]. If any of the computed signatures match any of the v1 signatures in the header, the payload is considered authentic [4][5]. Official Stripe SDKs are designed to handle this automatically [8]. When you pass the expected secret (or multiple secrets) to the SDK's verification functions (e.g., ConstructEvent), the library handles the extraction of the header components, the timing-safe comparison, and the iteration through multiple signatures to ensure a seamless rotation without downtime [4][5][8]. If you are implementing manual verification, ensure your logic iterates through all v1 signatures found in the header rather than assuming only one exists [4][5].

Citations:


Handle every v1 signature, not just one.

The current dict comprehension drops duplicate v1 entries, retaining only the last one. During Stripe secret rotation, multiple secrets can be active simultaneously (Stripe allows keeping the old secret active for up to 24 hours), and Stripe emits a v1 signature for each active secret in the header (e.g., t=1495999758,v1=ABC,v1=DEF). The code must iterate through all v1 signatures in the header and verify the payload against each corresponding stored secret, accepting the request if any match.

Use Header.get_list("Stripe-Signature") or parse the raw header string to collect all values for the v1 key, then loop through them for comparison instead of assuming a single value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autogpt_platform/backend/backend/integrations/webhooks/stripe.py` around
lines 50 - 73, The Stripe webhook verification in the signature-parsing logic
only keeps one v1 value, so update the parsing in the webhook handler to collect
every v1 signature from the Stripe-Signature header instead of using a dict
comprehension that overwrites duplicates. In the same verification flow that
builds signed_payload and compares against expected, loop through all parsed v1
signatures and accept the request if any one matches the computed HMAC for the
active signing secret(s), preserving the timestamp checks and existing
HTTPException behavior for invalid headers.
Comment on lines +85 to +91
payload = await request.json()
event_type = payload.get("type", "")
if not event_type:
raise HTTPException(
status_code=400, detail="Stripe event type missing from payload"
)
return payload, event_type

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Map the stripe.py file structure first
ast-grep outline autogpt_platform/backend/backend/integrations/webhooks/stripe.py --match StripeWebhookHandler --view expanded

Repository: Significant-Gravitas/AutoGPT

Length of output: 245


🏁 Script executed:

#!/bin/bash
# Find the integrations router and locate where validate_payload() is called
rg 'validate_payload' --type py -A 5 -B 5 autogpt_platform/backend/backend/integrations/

Repository: Significant-Gravitas/AutoGPT

Length of output: 7239


🏁 Script executed:

#!/bin/bash
# Check for exception handlers in the integrations router or app
rg 'class.*ExceptionHandler|`@app.exception_handler`|`@router.exception_handler`' --type py -A 10 autogpt_platform/backend/backend/integrations/

Repository: Significant-Gravitas/AutoGPT

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Read the full stripe.py file to see validate_payload implementation
cat -n autogpt_platform/backend/backend/integrations/webhooks/stripe.py

Repository: Significant-Gravitas/AutoGPT

Length of output: 6504


🏁 Script executed:

#!/bin/bash
# Find the integrations router and the handler that calls validate_payload
fd --type f --hidden false -g '*router*.py' autogpt_platform/backend/backend/integrations/ | xargs -I {} rg 'validate_payload|`@app`\.route|`@router`\.post' -A 3 -B 3 --no-heading

Repository: Significant-Gravitas/AutoGPT

Length of output: 224


🏁 Script executed:

#!/bin/bash
# Search for exception handlers in the integrations module
rg 'exception_handler' --type py -A 5 autogpt_platform/backend/backend/integrations/

Repository: Significant-Gravitas/AutoGPT

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Find files containing the webhook route handler
rg 'class.*Router|`@app`\.(post|get|api_route)|webhook_endpoint' --type py autogpt_platform/backend/backend/integrations/ | head -20

Repository: Significant-Gravitas/AutoGPT

Length of output: 412


🏁 Script executed:

#!/bin/bash
# Search for the file containing the webhook handling route
rg 'validate_payload' --type py -l | xargs -I {} rg 'validate_payload|@.*\.post' -A 10 -B 5 {}

Repository: Significant-Gravitas/AutoGPT

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Check for global exception handlers in the backend app
find autogpt_platform/backend -name "*.py" -exec rg 'exception_handler|HTTPException' {} \; -print | head -50

Repository: Significant-Gravitas/AutoGPT

Length of output: 2694


🏁 Script executed:

#!/bin/bash
# Search for webhook route handlers in the integrations API
rg 'webhook|validate_payload' --type py -A 10 -B 5 autogpt_platform/backend/backend/api/external/v1/integrations.py

Repository: Significant-Gravitas/AutoGPT

Length of output: 751


🏁 Script executed:

#!/bin/bash
# Check for global exception handlers in the main app
rg 'exception_handler' --type py -A 5 autogpt_platform/backend/backend/api/external/fastapi_app.py

Repository: Significant-Gravitas/AutoGPT

Length of output: 700


🏁 Script executed:

#!/bin/bash
# Check if there's a custom JSONDecodeError handler in the entire backend
rg 'JSONDecodeError' --type py -A 5 autogpt_platform/backend/

Repository: Significant-Gravitas/AutoGPT

Length of output: 32399


🏁 Script executed:

#!/bin/bash
# Check pyproject.toml for JSON library dependencies
grep -E 'orjson|ujson|json' autogpt_platform/backend/pyproject.toml

Repository: Significant-Gravitas/AutoGPT

Length of output: 288


🏁 Script executed:

#!/bin/bash
# Search for FastAPI app initialization and JSON configuration
rg 'FastAPI|json_module|json_loads' --type py -A 5 -B 5 autogpt_platform/backend/backend/api/external/fastapi_app.py | head -50

Repository: Significant-Gravitas/AutoGPT

Length of output: 1263


🏁 Script executed:

#!/bin/bash
# Check what exception is raised by request.json() in FastAPI by looking at the Starlette source or docs
# For now, let's check if orjson is the default by inspecting the app setup
find autogpt_platform/backend -name "fastapi_app.py" -o -name "app.py" | xargs rg 'FastAPI' -A 20 | head -30

Repository: Significant-Gravitas/AutoGPT

Length of output: 3095


🏁 Script executed:

#!/bin/bash
# Verify orjson exception name and import
python3 - <<'PY'
try:
    import orjson
    print("Exception name:", orjson.JSONDecodeError)
    print("Bases:", orjson.JSONDecodeError.__bases__)
except Exception as e:
    print("Error:", e)
PY

Repository: Significant-Gravitas/AutoGPT

Length of output: 264


Return 400 for malformed JSON payloads in validate_payload()

await request.json() can raise orjson.JSONDecodeError (a subclass of json.JSONDecodeError) for malformed bodies. Since this exception is uncaught in validate_payload() and the integrations router lacks a specific handler for it, malformed requests currently result in a 500 Internal Server Error instead of a clean 400 Bad Request.

Add explicit handling to catch JSON decode errors and return a 400 response:

Suggested fix
+from orjson import JSONDecodeError
 import hashlib
 import hmac
 import logging
 import time
@@
     async def validate_payload(
         cls,
         webhook: integrations.Webhook,
         request: Request,
         credentials: Credentials | None,
     ) -> tuple[dict, str]:
-        payload = await request.json()
+        try:
+            payload = await request.json()
+        except JSONDecodeError:
+            raise HTTPException(status_code=400, detail="Invalid JSON payload")
+
         event_type = payload.get("type", "")
         if not event_type:
             raise HTTPException(
                 status_code=400, detail="Stripe event type missing from payload"
             )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autogpt_platform/backend/backend/integrations/webhooks/stripe.py` around
lines 85 - 91, `validate_payload()` in the Stripe webhook flow should handle
malformed JSON before reading `payload["type"]`; wrap `await request.json()`
with explicit catching of JSON decode errors such as
`orjson.JSONDecodeError`/`json.JSONDecodeError` and raise
`HTTPException(status_code=400, ...)` instead of letting the exception bubble.
Keep the existing missing-event-type 400 behavior unchanged, and make sure the
fix is localized to `validate_payload()` in the Stripe webhook integration.
@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 57.44681% with 60 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.69%. Comparing base (e2711b1) to head (7c97a4c).
⚠️ Report is 3 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev   #13435      +/-   ##
==========================================
- Coverage   74.71%   74.69%   -0.03%     
==========================================
  Files        2537     2540       +3     
  Lines      192315   192448     +133     
  Branches    18925    18936      +11     
==========================================
+ Hits       143697   143747      +50     
- Misses      44493    44579      +86     
+ Partials     4125     4122       -3     
Flag Coverage Δ
platform-backend 81.96% <57.44%> (-0.02%) ⬇️
platform-frontend-e2e 31.98% <ø> (-0.40%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Platform Backend 81.96% <57.44%> (-0.02%) ⬇️
Platform Frontend 47.44% <ø> (-0.11%) ⬇️
AutoGPT Libs ∅ <ø> (∅)
Classic AutoGPT 28.43% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Stripe as a webhook provider so agents can trigger directly from Stripe subscription lifecycle events (created/updated/deleted), avoiding internal/demo accounts counted in the platform DB.

Changes:

  • Introduces ProviderName.STRIPE and wires a new StripeWebhooksManager into webhook manager loading.
  • Implements Stripe webhook endpoint registration/deregistration and signature verification.
  • Adds a new StripeSubscriptionTriggerBlock with sample event payloads for the block test runner.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
autogpt_platform/backend/backend/integrations/webhooks/stripe.py New Stripe webhook manager: registration/deregistration, payload validation, signature verification.
autogpt_platform/backend/backend/integrations/webhooks/init.py Registers StripeWebhooksManager in the webhook manager loader.
autogpt_platform/backend/backend/integrations/providers.py Adds ProviderName.STRIPE.
autogpt_platform/backend/backend/blocks/stripe/triggers.py Adds StripeSubscriptionTriggerBlock webhook-trigger block and output parsing.
autogpt_platform/backend/backend/blocks/stripe/_auth.py Adds Stripe API-key credential field plus test credential metadata.
autogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.created.json Adds example payload used by the block test runner.
autogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.updated.json Adds example payload used by the block test runner.
autogpt_platform/backend/backend/blocks/stripe/init.py Declares the new Stripe blocks package.
)

super().__init__(
id="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"provider": TEST_CREDENTIALS.provider,
"id": TEST_CREDENTIALS.id,
"type": TEST_CREDENTIALS.type,
"title": TEST_CREDENTIALS.type,
Comment on lines +107 to +110
# Build form-encoded body for Stripe API (requires form encoding, not JSON)
form_data = f"url={ingress_url}"
for event in events:
form_data += f"&enabled_events[]={event}"
Comment on lines +52 to +76
timestamp = parts.get("t")
v1_sig = parts.get("v1")
if not timestamp or not v1_sig:
raise HTTPException(
status_code=403, detail="Invalid Stripe-Signature format"
)

# Reject stale timestamps
if abs(time.time() - int(timestamp)) > STRIPE_TIMESTAMP_TOLERANCE:
raise HTTPException(
status_code=403, detail="Stripe webhook timestamp is too old"
)

payload_body = await request.body()
signed_payload = f"{timestamp}.{payload_body.decode('utf-8')}"
expected = hmac.new(
signing_secret.encode("utf-8"),
msg=signed_payload.encode("utf-8"),
digestmod=hashlib.sha256,
).hexdigest()

if not hmac.compare_digest(expected, v1_sig):
raise HTTPException(
status_code=403, detail="Stripe webhook signature mismatch"
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2 participants