feat(stripe): Stripe subscription webhook trigger blocks#13435
feat(stripe): Stripe subscription webhook trigger blocks#13435Torantulino wants to merge 1 commit into
Conversation
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.
|
This PR targets the Automatically setting the base branch to |
WalkthroughAdds Stripe provider support across credentials, webhook management, and subscription trigger handling, plus example payload fixtures for created and updated subscription events. ChangesStripe support
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
🔍 PR Overlap DetectionThis check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early. 🟢 Low Risk — File Overlap OnlyThese 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: |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
autogpt_platform/backend/backend/blocks/stripe/triggers.py (1)
85-87: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove
StripeWebhookTypeto 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
📒 Files selected for processing (8)
autogpt_platform/backend/backend/blocks/stripe/__init__.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.created.jsonautogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.updated.jsonautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/integrations/webhooks/__init__.pyautogpt_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: Usepoetry run ...command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies likeopenpyxl
Use absolute imports withfrom 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 — avoidhasattr/getattr/isinstancefor 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%sfor deferred interpolation indebuglog statements for efficiency; use f-strings elsewhere for readability (e.g.,logger.debug("Processing %s items", count)vslogger.info(f"Processing {count} items"))
Sanitize error paths by usingos.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
Usetransaction=Truefor Redis pipelines to ensure atomicity on multi-step operations
Usemax(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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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.jsonautogpt_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, usestore_media_file()withreturn_format="for_local_processing"when processing with local tools (ffmpeg, MoviePy, PIL)
For blocks handling files, usestore_media_file()withreturn_format="for_external_api"when sending content to external APIs (Replicate, OpenAI)
For blocks returning files, usestore_media_file()withreturn_format="for_block_output"to enable auto-adaptation to execution context (workspace:// in CoPilot, data URI in graphs)
When creating new blocks, inherit fromBlockbase class, define input/output schemas usingBlockSchema, implement asyncrunmethod, and generate unique block ID usinguuid.uuid4()
Files:
autogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.created.jsonautogpt_platform/backend/backend/blocks/stripe/example_payloads/customer.subscription.updated.jsonautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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__.pyautogpt_platform/backend/backend/integrations/providers.pyautogpt_platform/backend/backend/blocks/stripe/_auth.pyautogpt_platform/backend/backend/blocks/stripe/triggers.pyautogpt_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.pyautogpt_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.pyautogpt_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.pyautogpt_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.pyautogpt_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.pyautogpt_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.pyautogpt_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!
| def StripeCredentialsField() -> StripeCredentialsInput: | ||
| return CredentialsField( | ||
| description="Stripe API secret key (sk_live_... or sk_test_...). " | ||
| "Requires permission to create and delete webhook endpoints.", | ||
| ) |
There was a problem hiding this comment.
🎯 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.
| 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.
| events: EventsFilter = SchemaField( | ||
| title="Events", | ||
| description="Subscription lifecycle events to subscribe to", | ||
| ) |
There was a problem hiding this comment.
🎯 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.
| 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.
| error: str = SchemaField( | ||
| description="Error message if the payload could not be parsed" | ||
| ) |
There was a problem hiding this comment.
🎯 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
| # 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): |
There was a problem hiding this comment.
🩺 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:
- 1: https://docs.stripe.com/webhooks?lang=node
- 2: https://docs.stripe.com/webhooks
- 3: https://docs.stripe.com/webhooks?verify=verify-manually
- 4: https://github.com/stripe/stripe-go/blob/master/webhooks.go
- 5: https://github.com/stripe/stripe-go/blob/master/webhook/client.go
- 6: https://docs.stripe.com/webhooks/signature
- 7: https://github.com/stripe/stripe-php/blob/master/lib/WebhookSignature.php
- 8: https://www.hooklistener.com/learn/stripe-webhook-security-guide
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.
| 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 |
There was a problem hiding this comment.
🩺 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 expandedRepository: 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.pyRepository: 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-headingRepository: 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 -20Repository: 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 -50Repository: 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.pyRepository: 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.pyRepository: 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.tomlRepository: 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 -50Repository: 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 -30Repository: 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)
PYRepository: 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 Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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.STRIPEand wires a newStripeWebhooksManagerinto webhook manager loading. - Implements Stripe webhook endpoint registration/deregistration and signature verification.
- Adds a new
StripeSubscriptionTriggerBlockwith 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, |
| # 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}" |
| 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" | ||
| ) |
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"inproviders.pyWebhook manager (
integrations/webhooks/stripe.py)whsec_...) inwebhook.config(returned once on creation)Stripe-Signatureheader using timestamp + HMAC-SHA256 with 5-min replay toleranceBlocks (
blocks/stripe/)_auth.py— API-key credentials (sk_live_.../sk_test_...)triggers.py—StripeSubscriptionTriggerBlocksubscription_id,customer_id,status,plan_name,plan_interval,amount_cents,currency,livemode, fullpayloadexample_payloads/— realistic example payloads for unit testsUsage
customer_id,plan_name) to a Discord notification blockTesting
customer.subscription.createdandcustomer.subscription.updatedtest_input/test_outputwired in the block__init__for the existing test runner