Skip to content

feat(api): add app endpoints to v3#3767

Draft
tothandras wants to merge 16 commits intomainfrom
feat/v3-api-apps
Draft

feat(api): add app endpoints to v3#3767
tothandras wants to merge 16 commits intomainfrom
feat/v3-api-apps

Conversation

@tothandras
Copy link
Copy Markdown
Contributor

@tothandras tothandras commented Jan 12, 2026

Summary by CodeRabbit

New Features

  • App Catalog: Discover and browse available billing applications including Stripe, Custom Invoicing, and Sandbox environments.
  • Stripe Integration: Process payments with native Stripe checkout sessions and webhook event handling.
  • Custom Invoicing: Synchronize invoice drafts and finalized invoices with external billing systems, including payment status tracking.
  • OAuth2 Support: Enable secure app authorization using industry-standard OAuth2 flow.
  • App Capabilities Framework: Apps now support usage reporting, event tracking, tax calculation, invoice delivery, and payment collection.

✏️ Tip: You can customize this high-level summary in your review settings.

@tothandras tothandras added the release-note/feature Release note: Exciting New Features label Jan 12, 2026
@tothandras tothandras requested a review from a team as a code owner January 12, 2026 12:07
@tothandras tothandras marked this pull request as draft January 12, 2026 12:07
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 12, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This PR adds a comprehensive Apps/Billing integration layer to the API, introducing app catalog management, Stripe checkout integration, custom invoicing synchronization, and OAuth2 flows. It includes updated linting rules for enum naming conventions, extensive TypeSpec definitions, generated Go API types, HTTP handlers, and server routing integration. All handler implementations are currently scaffolded with NotImplemented errors.

Changes

Cohort / File(s) Summary
API Spec Linting Rules
api/spec/lib/index.js, api/spec/lib/rules/casing.js, api/spec/lib/rules/friendly-name.js, api/spec/lib/rules/utils.js
Added new linting rules for AIP enum casing (PascalCase for names/members, snake_case for values). Introduced casingAIPErrorsRule and friendlyNameAIPRule to enforce naming conventions and friendly-name metadata on AIP enums. Updated dynamic regex generation to support PascalCase exceptions.
Apps Core TypeSpec Definitions
api/spec/src/v3/apps/app.tsp, api/spec/src/v3/apps/capability.tsp, api/spec/src/v3/apps/catalog.tsp, api/spec/src/v3/apps/sandbox.tsp, api/spec/src/v3/apps/index.tsp
Defines base app model, enums (AppType, AppStatus, AppCapabilityType, AppInstallMethods), app capability schema, and app catalog item structure. Sandbox app extends the base app type. All include friendly-name and lifecycle visibility decorators.
Apps Integration TypeSpec Definitions
api/spec/src/v3/apps/oauth.tsp, api/spec/src/v3/apps/stripe.tsp, api/spec/src/v3/apps/custominvoicing.tsp
Comprehensive Stripe and OAuth2 integration schemas with 684 lines of Stripe checkout options, webhook models, custom invoicing sync request/response types, and OAuth2 authorization grant models with extensive field-level configuration.
Apps Operations & Service Integration
api/spec/src/v3/apps/operations.tsp, api/spec/src/v3/konnect.tsp, api/spec/src/v3/openmeter.tsp
Defines operation interfaces for app catalog, custom invoicing, and Stripe endpoints. Integrates apps endpoints into both Konnect and OpenMeter service namespaces with appropriate routing and tagging.
Billing Enums & Scalar Updates
api/spec/src/v3/entitlements/entitlements.tsp, api/spec/src/v3/entitlements/access.tsp, api/spec/src/v3/subscriptions/subscription.tsp, api/spec/src/v3/subscriptions/operations.tsp, api/spec/src/v3/invoices/invoice.tsp, api/spec/src/v3/shared/consts.tsp
Standardized enum member naming to PascalCase with explicit string mappings (Metered/Immediate/etc.). Added AppsTag/AppsDescription constants and InvoiceNumber scalar type.
Generated Go API Types & Interfaces
api/v3/api.gen.go
Generated ~1342 lines of public Go types including app enum constants, catalog/Stripe/custom invoicing models, union-type handlers, and new ServerInterface endpoints for all app operations.
Go HTTP Handler Scaffolding
api/v3/handlers/apps/handler.go, api/v3/handlers/apps/list_app_catalog.go, api/v3/handlers/apps/get_app_catalog_item*.go, api/v3/handlers/apps/submit_custom_invoicing_*.go, api/v3/handlers/apps/create_stripe_checkout_session.go, api/v3/handlers/apps/handle_stripe_webhook.go
Introduces 8 new HTTP handler files for app catalog listing/retrieval, custom invoicing draft/issuing sync, payment status updates, Stripe checkout sessions, and webhook handling. All currently return NotImplemented errors with full request/response type wiring.
Go Server Integration
api/v3/server/server.go, api/v3/server/routes.go
Adds appsHandler field to Server struct and implements 8 new route methods delegating to the apps handler for catalog and integration endpoints.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Adds custom invoicing app type with related API surface and integrations for draft/issuing synchronization workflows.
  • Enables apps/billing functionality by default through feature flag updates.
  • Adds similar Stripe checkout session surface with checkout configuration and result modeling alongside this PR's implementation.

Suggested labels

area/api

Suggested reviewers

  • turip
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding app endpoints to the v3 API. It's specific enough that reviewers scanning history would immediately understand the scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/v3-api-apps

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/spec/src/v3/subscriptions/operations.tsp (1)

30-31: SubscriptionChange is omitting properties that don't exist on SubscriptionCreate.

The OmitProperties<SubscriptionCreate, "customer_id" | "customer_key"> won't work as intended because SubscriptionCreate doesn't have top-level customer_id or customer_key properties—it has a customer object with nested id and key fields instead. If the goal is to prevent changes to the customer assignment in a subscription update, you'll probably want to omit "customer" directly.

🤖 Fix all issues with AI agents
In @api/spec/src/v3/apps/capability.tsp:
- Around line 42-47: The description string for the AppCapability example (entry
with AppCapabilityType.CollectPayments, key "stripe_collect_payment", name
"Collect Payments") has a grammar nit; update the description to "Stripe
Payments collects outstanding revenue using the customer’s default payment
method." (or "Stripe Payments collects outstanding revenue with the customer's
default payment method.") so the product name is capitalized and the phrase
about the default payment method reads clearly.

In @api/spec/src/v3/apps/catalog.tsp:
- Around line 1-4: catalog.tsp currently imports app.tsp creating a circular
import with app.tsp; extract the shared types (e.g., AppType and AppStatus) into
a new module (for example shared/app-types.tsp) and update both catalog.tsp and
app.tsp to import those types instead of importing each other, ensuring no
runtime circular dependency remains.
- Around line 33-61: In the example block for AppType.Stripe update the
description string on the AppType.Stripe example from "Stripe interation allows
you to collect payments with Stripe." to "Stripe integration allows you to
collect payments with Stripe." and make capability keys consistent (use a single
form such as "stripe_collect_payments" for the CollectPayments capability) by
updating the key value in the corresponding capability object; locate these in
the example for AppType.Stripe where capabilities are defined and adjust the
description and the CollectPayments capability.key to the chosen consistent
name.

In @api/spec/src/v3/apps/operations.tsp:
- Around line 35-42: The getOAuth2InstallURL operation currently declares its
return as Shared.GetResponse<AppCatalogItem> but should return the OAuth2
install URL shape; update the return type to
Shared.GetResponse<AppOAuth2InstallResponse> (preserving the existing
Common.NotFound | Common.ErrorResponses union) so the endpoint returns the
AppOAuth2InstallResponse model defined in oauth.tsp; locate and modify the
method declaration for getOAuth2InstallURL to use AppOAuth2InstallResponse in
the Shared.GetResponse wrapper.

In @api/v3/handlers/apps/get_app_catalog_item_oauth2_install_url.go:
- Around line 14-16: The response alias is wrong: replace
GetAppCatalogItemOauth2InstallUrlResponse = api.BillingAppCatalogItem with a new
simple response type that returns only the install URL (e.g., define
api.BillingAppOauth2InstallUrlResponse struct with a single string field like
InstallUrl or Url) and update the handler typedef
GetAppCatalogItemOauth2InstallUrlHandler to use that new response type; add the
new type into the api package and ensure any code that constructs or returns the
response uses the InstallUrl field.

In @api/v3/handlers/apps/handle_stripe_webhook.go:
- Around line 14-46: Update HandleStripeWebhookRequest to include RawPayload
[]byte and StripeSignature string and populate them in the request factory
before parsing the JSON body: read the raw request body bytes (io.ReadAll) into
RawPayload, capture the Stripe-Signature header via
r.Header.Get("Stripe-Signature"), then parse the JSON into Body from those raw
bytes (e.g., json.Unmarshal or io.NopCloser + bytes.NewReader) so signature
verification can later use RawPayload and StripeSignature while preserving the
existing Body field (api.BillingAppStripeWebhookEvent) and existing handler
names (HandleStripeWebhookRequest, HandleStripeWebhook).
🧹 Nitpick comments (12)
api/spec/lib/rules/casing.js (1)

110-133: Good separation of concerns!

Splitting the value check into its own rule with error severity (vs warning for names) is a nice touch - it clearly signals that incorrect enum values are a harder failure than naming style issues.

One small thing: the description on line 113 is identical to casingAIPRule. Consider making it more specific, e.g., "Ensure enum values use snake_case for AIP naming conventions." to help differentiate them in lint output.

📝 Suggested description tweak
 export const casingAIPErrorsRule = createRule({
   name: 'casing-aip-errors',
   severity: 'error',
-  description: 'Ensure proper casing style for AIP naming conventions.',
+  description: 'Ensure enum values use snake_case for AIP naming conventions.',
   messages: {
api/spec/lib/rules/friendly-name.js (2)

84-112: Nice AIP-specific interface handling!

The logic for avoiding friendlyName on *Endpoints and *Operations interfaces while requiring it on others is clear and well-structured.

One tiny edge case to consider: unlike the model, enum, and union handlers which check node.name &&, the interface handler accesses node.name directly on line 89. If node.name could ever be undefined, this would throw. TypeSpec interfaces should always have names in practice, but adding a guard would make it bulletproof.

🛡️ Optional defensive check
     interface: (node) => {
+      if (!node.name) return
+
       const hasFriendlyName = node.decorators.some(
         (d) => d.decorator.name === '$friendlyName',
       )

114-158: Heads up: duplicated handlers between rules

The model, enum, and union handlers in friendlyNameAIPRule are identical to those in friendlyNameRule. Not urgent, but if you ever need to update the logic, you'd have to change it in two places.

Could extract these into shared helper functions down the road if this file grows.

api/v3/handlers/apps/handler.go (1)

25-33: Consider validating resolveNamespace isn't nil.

If a caller accidentally passes nil for resolveNamespace, you'll get a nil pointer panic later when it's invoked. A quick nil check in the constructor could save some debugging headaches:

💡 Optional defensive check
 func New(
 	resolveNamespace func(ctx context.Context) (string, error),
 	options ...httptransport.HandlerOption,
 ) Handler {
+	if resolveNamespace == nil {
+		panic("resolveNamespace cannot be nil")
+	}
 	return &handler{
 		resolveNamespace: resolveNamespace,
 		options:          options,
 	}
 }
api/v3/handlers/apps/submit_custom_invoicing_issuing_synchronized.go (1)

24-46: Tiny readability win: avoid naming the op arg request.
In Line 37 the parameter name request is a bit confusing next to the imported request package used above; I’d rename it to req.

Proposed tweak
-		func(ctx context.Context, request SubmitCustomInvoicingIssuingSynchronizedRequest) (SubmitCustomInvoicingIssuingSynchronizedResponse, error) {
+		func(ctx context.Context, req SubmitCustomInvoicingIssuingSynchronizedRequest) (SubmitCustomInvoicingIssuingSynchronizedResponse, error) {
 			return nil, apierrors.NewNotImplementedError(ctx, nil)
 		},
api/spec/src/v3/apps/app.tsp (2)

45-69: Consider whether AppBase.definition should embed the whole AppCatalogItem.
If installed-app reads are frequent, embedding the full catalog item may be heavier than needed; an ID/key reference (or a slim “catalog item ref”) can reduce coupling. If you want the embed, this is totally fine—just calling out the tradeoff early.


1-3: Consider consolidating these tightly coupled modules to avoid circular import.

Both app.tsp and catalog.tsp import each other—TypeSpec can handle this (it supports circular references via its emitter), but it's worth restructuring for cleaner code organization. You've got a few options:

  1. Merge them into a single file if they're tightly coupled
  2. Extract shared types (e.g., AppType, AppStatus) into a separate shared module that both files import
  3. Keep them separate but eliminate the back-reference by restructuring how AppBase and AppCatalogItem relate

This will make the dependency graph clearer and easier to maintain long-term.

api/spec/src/v3/apps/capability.tsp (1)

48-68: Make AppCapability explicitly read-only if it’s catalog-only.
Given it’s used under AppCatalogItem.capabilities (which is read-only), I’d also mark the fields of AppCapability with @visibility(Lifecycle.Read) for consistency and to avoid accidental use in write models later.

Proposed tweak
 model AppCapability {
   /**
    * Type of the capability.
    */
+  @visibility(Lifecycle.Read)
   type: AppCapabilityType;

   /**
    * Key of the capability.
    */
+  @visibility(Lifecycle.Read)
   key: Shared.ResourceKey;

   /**
    * Name of the capability.
    */
+  @visibility(Lifecycle.Read)
   name: string;

   /**
    * Description of the capability.
    */
+  @visibility(Lifecycle.Read)
   description: string;
 }
api/v3/handlers/apps/update_custom_invoicing_payment_status.go (1)

37-39: Optional: rename op parameter requestreq for clarity.
Keeps things easier to scan alongside the imported request package used above.

api/spec/src/v3/konnect.tsp (1)

69-81: Route consistency nit: consider aligning app-catalog under /openmeter/apps/*.

/openmeter/app-catalog is the odd one out vs /openmeter/apps/custom-invoicing and /openmeter/apps/stripe. If that’s intentional (e.g., “catalog is shared, not an app instance”), all good—otherwise I’d rename to something like /openmeter/apps/catalog for consistency.

api/v3/handlers/apps/create_stripe_checkout_session.go (1)

14-39: Handler scaffold is consistent; small readability tweak + please verify body-size limits.

  • Readability: you can decode into CreateStripeCheckoutSessionRequest directly (no need for the concrete api.* name).
  • Also worth double-checking request.ParseBody has a sane max body size (to avoid easy memory pressure attacks on public endpoints).
Proposed tweak
-		func(ctx context.Context, r *http.Request) (CreateStripeCheckoutSessionRequest, error) {
-			body := api.BillingAppStripeCreateCheckoutSessionRequest{}
+		func(ctx context.Context, r *http.Request) (CreateStripeCheckoutSessionRequest, error) {
+			var body CreateStripeCheckoutSessionRequest
 			if err := request.ParseBody(r, &body); err != nil {
 				return CreateStripeCheckoutSessionRequest{}, err
 			}
 
 			return body, nil
 		},
api/spec/src/v3/apps/stripe.tsp (1)

65-68: Consider int64 for Unix timestamp to avoid Y2038 issue.

The created field uses int32 for the Unix timestamp. While this matches how Stripe currently sends timestamps, signed 32-bit integers will overflow after January 2038. This is a minor future-proofing concern - not urgent but worth considering.

Suggested change
   /**
    * Unix timestamp when the event was created.
    */
-  created: int32;
+  created: int64;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd7b67f and a862c4f.

⛔ Files ignored due to path filters (1)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
📒 Files selected for processing (33)
  • api/spec/lib/index.js
  • api/spec/lib/rules/casing.js
  • api/spec/lib/rules/friendly-name.js
  • api/spec/lib/rules/utils.js
  • api/spec/src/v3/apps/app.tsp
  • api/spec/src/v3/apps/capability.tsp
  • api/spec/src/v3/apps/catalog.tsp
  • api/spec/src/v3/apps/custominvoicing.tsp
  • api/spec/src/v3/apps/index.tsp
  • api/spec/src/v3/apps/oauth.tsp
  • api/spec/src/v3/apps/operations.tsp
  • api/spec/src/v3/apps/sandbox.tsp
  • api/spec/src/v3/apps/stripe.tsp
  • api/spec/src/v3/entitlements/access.tsp
  • api/spec/src/v3/entitlements/entitlements.tsp
  • api/spec/src/v3/invoices/invoice.tsp
  • api/spec/src/v3/konnect.tsp
  • api/spec/src/v3/openmeter.tsp
  • api/spec/src/v3/shared/consts.tsp
  • api/spec/src/v3/subscriptions/operations.tsp
  • api/spec/src/v3/subscriptions/subscription.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/apps/create_stripe_checkout_session.go
  • api/v3/handlers/apps/get_app_catalog_item.go
  • api/v3/handlers/apps/get_app_catalog_item_oauth2_install_url.go
  • api/v3/handlers/apps/handle_stripe_webhook.go
  • api/v3/handlers/apps/handler.go
  • api/v3/handlers/apps/list_app_catalog.go
  • api/v3/handlers/apps/submit_custom_invoicing_draft_synchronized.go
  • api/v3/handlers/apps/submit_custom_invoicing_issuing_synchronized.go
  • api/v3/handlers/apps/update_custom_invoicing_payment_status.go
  • api/v3/server/routes.go
  • api/v3/server/server.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*.tsp

⚙️ CodeRabbit configuration file

**/*.tsp: Review the TypeSpec code for conformity with TypeSpec best practices. When recommending changes also consider the fact that multiple codegeneration toolchains depend on the TypeSpec code, each of which have their idiosyncrasies and bugs.

The declared API should be accurate, in parity with the actual implementation, and easy to understand for the user.

Files:

  • api/spec/src/v3/apps/sandbox.tsp
  • api/spec/src/v3/invoices/invoice.tsp
  • api/spec/src/v3/subscriptions/operations.tsp
  • api/spec/src/v3/entitlements/access.tsp
  • api/spec/src/v3/shared/consts.tsp
  • api/spec/src/v3/openmeter.tsp
  • api/spec/src/v3/subscriptions/subscription.tsp
  • api/spec/src/v3/apps/app.tsp
  • api/spec/src/v3/apps/index.tsp
  • api/spec/src/v3/apps/catalog.tsp
  • api/spec/src/v3/entitlements/entitlements.tsp
  • api/spec/src/v3/konnect.tsp
  • api/spec/src/v3/apps/oauth.tsp
  • api/spec/src/v3/apps/operations.tsp
  • api/spec/src/v3/apps/capability.tsp
  • api/spec/src/v3/apps/custominvoicing.tsp
  • api/spec/src/v3/apps/stripe.tsp
**/*.go

⚙️ CodeRabbit configuration file

**/*.go: In general when reviewing the Golang code make readability and maintainability a priority, even potentially suggest restructuring the code to improve them.

Performance should be a priority in critical code paths. Anything related to event ingestion, message processing, database operations (regardless of database) should be vetted for potential performance bottlenecks.

Files:

  • api/v3/handlers/apps/submit_custom_invoicing_issuing_synchronized.go
  • api/v3/handlers/apps/handler.go
  • api/v3/handlers/apps/handle_stripe_webhook.go
  • api/v3/handlers/apps/get_app_catalog_item_oauth2_install_url.go
  • api/v3/handlers/apps/create_stripe_checkout_session.go
  • api/v3/handlers/apps/list_app_catalog.go
  • api/v3/server/server.go
  • api/v3/handlers/apps/submit_custom_invoicing_draft_synchronized.go
  • api/v3/handlers/apps/get_app_catalog_item.go
  • api/v3/handlers/apps/update_custom_invoicing_payment_status.go
  • api/v3/server/routes.go
  • api/v3/api.gen.go
🧠 Learnings (3)
📚 Learning: 2025-11-25T15:10:24.409Z
Learnt from: CR
Repo: openmeterio/openmeter PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T15:10:24.409Z
Learning: Generate API clients and OpenAPI spec via `make gen-api` from TypeSpec definitions

Applied to files:

  • api/spec/src/v3/openmeter.tsp
  • api/spec/src/v3/konnect.tsp
📚 Learning: 2025-11-07T12:29:09.087Z
Learnt from: chrisgacsal
Repo: openmeterio/openmeter PR: 3581
File: openmeter/notification/eventhandler/webhook.go:391-396
Timestamp: 2025-11-07T12:29:09.087Z
Learning: In the OpenMeter notification system (specifically in openmeter/notification/eventhandler/webhook.go), webhook channels are equivalent to notification rules. When sending webhook messages, the rule ID (event.Rule.ID) should be used as the channel identifier in the Channels field of SendMessageInput.

Applied to files:

  • api/v3/handlers/apps/handle_stripe_webhook.go
📚 Learning: 2025-11-07T11:28:40.430Z
Learnt from: chrisgacsal
Repo: openmeterio/openmeter PR: 3581
File: openmeter/notification/webhook/svix/webhook.go:253-257
Timestamp: 2025-11-07T11:28:40.430Z
Learning: In the openmeter notification system (specifically in openmeter/notification/webhook/svix/webhook.go), event type filtering for webhooks is not allowed or supported. Only channel-based filtering is used.

Applied to files:

  • api/v3/handlers/apps/handle_stripe_webhook.go
🧬 Code graph analysis (7)
api/v3/handlers/apps/handler.go (10)
api/v3/handlers/apps/list_app_catalog.go (1)
  • ListAppCatalogItemsHandler (16-16)
api/v3/handlers/apps/get_app_catalog_item.go (1)
  • GetAppCatalogItemHandler (16-16)
api/v3/handlers/apps/get_app_catalog_item_oauth2_install_url.go (1)
  • GetAppCatalogItemOauth2InstallUrlHandler (16-16)
api/v3/handlers/apps/submit_custom_invoicing_draft_synchronized.go (1)
  • SubmitCustomInvoicingDraftSynchronizedHandler (21-21)
api/v3/handlers/apps/submit_custom_invoicing_issuing_synchronized.go (1)
  • SubmitCustomInvoicingIssuingSynchronizedHandler (21-21)
api/v3/handlers/apps/update_custom_invoicing_payment_status.go (1)
  • UpdateCustomInvoicingPaymentStatusHandler (21-21)
api/v3/handlers/apps/create_stripe_checkout_session.go (1)
  • CreateStripeCheckoutSessionHandler (17-17)
api/v3/handlers/apps/handle_stripe_webhook.go (1)
  • HandleStripeWebhookHandler (21-21)
pkg/framework/transport/httptransport/options.go (1)
  • HandlerOption (19-21)
api/v3/api.gen.go (1)
  • Handler (2879-2881)
api/spec/lib/index.js (2)
api/spec/lib/rules/casing.js (2)
  • casingAIPErrorsRule (110-133)
  • casingAIPErrorsRule (110-133)
api/spec/lib/rules/friendly-name.js (2)
  • friendlyNameAIPRule (75-160)
  • friendlyNameAIPRule (75-160)
api/v3/handlers/apps/list_app_catalog.go (5)
api/v3/api.gen.go (2)
  • ListAppCatalogItemsParams (1578-1581)
  • AppCatalogItemPagePaginatedResponse (203-208)
pkg/framework/transport/httptransport/argshandler.go (2)
  • HandlerWithArgs (11-14)
  • NewHandlerWithArgs (18-29)
api/v3/apierrors/errors_ctors.go (1)
  • NewNotImplementedError (280-290)
pkg/framework/commonhttp/encoder.go (1)
  • JSONResponseEncoder (40-42)
pkg/framework/transport/httptransport/options.go (1)
  • AppendOptions (81-83)
api/v3/server/server.go (2)
api/v3/handlers/apps/handler.go (2)
  • Handler (9-18)
  • New (25-33)
pkg/framework/transport/httptransport/options.go (1)
  • WithErrorHandler (29-33)
api/v3/handlers/apps/get_app_catalog_item.go (5)
api/v3/api.gen.go (2)
  • BillingAppType (882-882)
  • BillingAppCatalogItem (311-326)
pkg/framework/transport/httptransport/argshandler.go (2)
  • HandlerWithArgs (11-14)
  • NewHandlerWithArgs (18-29)
api/v3/apierrors/errors_ctors.go (1)
  • NewNotImplementedError (280-290)
pkg/framework/commonhttp/encoder.go (1)
  • JSONResponseEncoder (40-42)
pkg/framework/transport/httptransport/options.go (3)
  • AppendOptions (81-83)
  • WithOperationName (41-47)
  • WithErrorEncoder (35-39)
api/v3/server/routes.go (3)
api/v3/server/server.go (1)
  • Server (98-110)
openmeter/server/server.go (1)
  • Server (27-29)
api/v3/api.gen.go (3)
  • ListAppCatalogItemsParams (1578-1581)
  • BillingAppType (882-882)
  • ULID (1507-1507)
api/v3/api.gen.go (1)
api/v3/handlers/apps/handler.go (1)
  • Handler (9-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Lint
  • GitHub Check: Artifacts / Container image
  • GitHub Check: Build
  • GitHub Check: Migration Checks
  • GitHub Check: Code Generators
  • GitHub Check: Test
  • GitHub Check: Repository Scan
  • GitHub Check: Analyze (go)
🔇 Additional comments (34)
api/spec/lib/rules/utils.js (2)

1-4: Nice approach with the exceptions list!

Defining pascalCaseExceptions as a separate constant makes it easy to extend later if more acronym exceptions are needed (e.g., HTTP, JSON, etc.). Clean and maintainable.


16-18: LGTM!

The dynamic regex construction is elegant. It correctly handles combinations like OAuth2URL or APIClient while still enforcing PascalCase for non-exception words.

api/spec/lib/index.js (2)

2-8: LGTM!

Imports are well-organized and align with the new rule exports.


45-56: Nice ruleset organization!

Good call keeping friendlyNameRule in the legacy ruleset while using friendlyNameAIPRule in the aip ruleset. This preserves backward compatibility for existing specs while enabling the new AIP conventions for new work.

api/spec/lib/rules/casing.js (1)

87-105: LGTM!

Clean implementation - checking both enum names and member names for PascalCase makes sense for AIP conventions.

api/spec/src/v3/subscriptions/subscription.tsp (3)

12-17: Nice enum pattern here!

Using PascalCase member names (Active, Inactive, etc.) with explicit lowercase string values ("active", "inactive") is a solid approach. It keeps the wire format stable while following TypeScript naming conventions for the generated code. This also means existing API consumers won't see any breaking changes in the serialized values. 👍


24-28: Consistent with the pattern above – looks good!

Same approach applied here with SubscriptionEditTimingEnum. The snake_case string values ("immediate", "next_billing_cycle") match what API consumers will see on the wire.


36-36: Example annotation updated correctly.

The example now references SubscriptionEditTimingEnum.Immediate to match the new member name.

api/spec/src/v3/subscriptions/operations.tsp (1)

19-24: Default value update looks correct.

The default now uses SubscriptionEditTimingEnum.Immediate to match the updated enum member name. Behavior stays the same – cancel immediately when no timing is specified.

api/spec/src/v3/entitlements/entitlements.tsp (1)

7-11: Nice enum refactor!

The switch to PascalCase members with explicit lowercase string mappings is a solid pattern. It keeps the serialized API output stable ("metered", "static", "boolean") while making the TypeSpec code more idiomatic. Good stuff.

api/spec/src/v3/entitlements/access.tsp (1)

14-16: Example updated correctly.

The @example(EntitlementType.Static) change properly aligns with the renamed enum member. Everything looks consistent.

api/v3/handlers/apps/handler.go (1)

9-18: Clean interface design.

The Handler interface is well-organized, grouping all app-related operations logically. The method names are self-documenting, and the return types align with the handler definitions in the other files.

api/spec/src/v3/apps/index.tsp (1)

1-1: Simple index file, looks good.

This pulls in the operations module to expose the Apps API surface. Clean and straightforward.

api/spec/src/v3/apps/sandbox.tsp (1)

1-11: Clean sandbox app definition.

Nice use of the spread operator with the generic AppBase<AppType.Sandbox> pattern. The documentation clearly explains the sandbox's purpose for testing billing features. Consistent with the other app type definitions.

api/v3/server/server.go (1)

16-16: LGTM! Apps handler integration looks good.

The wiring follows the existing patterns nicely. One thing to keep in mind: unlike the other handlers (eventsHandler, customersHandler, etc.) which receive their respective services, appsHandler currently only gets resolveNamespace. When the actual implementation lands, you'll likely need to add apps-related services to the Config struct and wire them through here.

Also applies to: 104-104, 143-143, 153-153

api/spec/src/v3/shared/consts.tsp (1)

20-22: Nice addition!

The new AppsTag and AppsDescription constants follow the established naming conventions and the description clearly explains what Apps are for. Good consistency with the other tag constants in this file.

api/spec/src/v3/invoices/invoice.tsp (1)

1-17: Looks solid!

The InvoiceNumber scalar is well-documented with clear explanations of the uniqueness considerations. The constraints (@minLength(1), @maxLength(256)) are reasonable.

I noticed there's no @pattern constraint for format validation — I'm guessing that's intentional since the format can vary depending on the app settings (as the docs mention). Just wanted to confirm that's the expected design here.

api/spec/src/v3/openmeter.tsp (2)

65-76: Routes look good, just noting the structure difference.

The new app-related endpoints are well-organized. I noticed the routing structure has:

  • /openmeter/app-catalog — at the top level
  • /openmeter/apps/custom-invoicing and /openmeter/apps/stripe — nested under /apps/

This makes sense semantically (catalog for discovery vs. specific app integrations), just wanted to confirm that's the intended design.


10-10: LGTM!

The apps import and tag metadata are wired up correctly, following the same pattern as the other tags in this file.

Also applies to: 40-40

api/v3/handlers/apps/submit_custom_invoicing_issuing_synchronized.go (1)

14-22: Watch out for *struct{} response + returning nil (encoder expectations).
You alias the response to *struct{} and later return nil; that’s fine if commonhttp.EmptyResponseEncoder never touches response. Worth double-checking to avoid a future nil deref if the encoder changes.

api/v3/handlers/apps/list_app_catalog.go (1)

13-34: Nice, consistent handler scaffolding.
Types/aliases + wiring match the established httptransport.NewHandlerWithArgs pattern and should be easy to fill in later.

api/v3/handlers/apps/update_custom_invoicing_payment_status.go (1)

14-22: Same note as the other 204 handlers: confirm nil is safe with EmptyResponseEncoder.
Returning nil for UpdateCustomInvoicingPaymentStatusResponse = *struct{} is fine as long as the encoder never inspects it.

Also applies to: 24-41

api/v3/server/routes.go (2)

9-41: Routes wiring looks clean and consistent with the rest of the server.
The .With(...).ServeHTTP(...) passthrough pattern matches the other domains and should be straightforward to maintain.


11-41: Good catch on the defensive thinking, but appsHandler is solid.

The sanity check confirms that appsHandler is always initialized—it's created via appshandler.New() in the NewServer() constructor (server.go:143) before the Server struct is returned. Since there's only one constructor and no test-only alternatives that bypass it, there's no path to a nil panic here.

api/spec/src/v3/apps/catalog.tsp (1)

87-92: Naming looks good! The install_methods field is in line with v3's established snake_case convention. All the other fields across v3 TypeSpec follow the same pattern (like event_type, value_property, enable_draft_sync_hook, etc.), so this fits right in and won't stick out in generated SDKs or OpenAPI.

api/spec/src/v3/konnect.tsp (2)

11-11: New Apps import looks good — please confirm codegen stays clean.

Since this expands the public surface, it’d be great to run make gen-api and ensure TypeSpec/OpenAPI/Go generation stays stable. Based on learnings, make gen-api is the expected workflow here.


35-35: Nice: Apps tag metadata is now first-class in Konnect.

This should make the new endpoints discoverable and documented consistently alongside the other tags.

api/v3/handlers/apps/get_app_catalog_item.go (1)

13-33: Looks good; please confirm BillingAppType validation happens somewhere.

Right now the arg decoder is a passthrough, which is fine if routing/binding guarantees only allowed BillingAppType values reach this handler—just worth confirming so we don’t end up with “unknown app type” silently flowing into business logic later.

api/spec/src/v3/apps/custominvoicing.tsp (1)

7-321: Verify the spec's invoice number generation and immutability claims against implementation.

The spec makes explicit behavioral guarantees: auto-generated invoice numbers with "INV-" prefix and immutability once invoices reach the issued state. The test suite validates against "INV-TECU-1" format when hooks are disabled, so the behavior is being tested. However, I couldn't locate the complete code path for the "INV-" prefix generation in the billing service's sequence definitions—only GATHER and DRAFT prefixes are defined.

Before marking this complete, confirm:

  • Where the "INV-" prefix is actually generated (it may be state-specific or in a code path not yet located)
  • Whether the immutability guarantee at the issued state is enforced in the invoice state machine
  • That all three behavioral claims (prefix generation, number replacement, immutability timing) are consistently implemented

If these are real guarantees, all good; if they're aspirational, consider softening the language in the spec docs.

api/v3/handlers/apps/submit_custom_invoicing_draft_synchronized.go (1)

1-47: Clean scaffolding for the draft synchronization endpoint.

The handler follows the established pattern nicely - request/response type definitions, proper body parsing, and the NotImplemented placeholder. The 204 No Content response encoder is appropriate for this void-returning endpoint. Looks good to go!

api/spec/src/v3/apps/operations.tsp (1)

44-174: Well-documented custom invoicing and Stripe operations.

The operation interfaces are nicely structured with clear documentation explaining the purpose of each endpoint. The inline comments describing when each callback should be invoked (draft sync, issuing sync, payment status updates) will be super helpful for integration developers. The Stripe operations with links to Stripe's documentation are a nice touch too.

api/spec/src/v3/apps/oauth.tsp (1)

1-162: Solid OAuth2 flow modeling per RFC 6749.

This file does a great job capturing the OAuth2 authorization code grant flow. The suppress directives for casing on error_description and error_uri are the right call since the OAuth2 spec mandates those exact names. The RFC references in the comments make it easy to cross-reference. The composite model approach for combining success and error params makes sense given how OAuth2 callbacks work.

api/spec/src/v3/apps/stripe.tsp (1)

1-64: Comprehensive Stripe Checkout integration surface.

Really thorough coverage of the Stripe Checkout Session API here. The customer union type (StripeCustomerId | StripeCustomerKey | StripeCustomerCreate) is a nice ergonomic touch for API consumers. Good use of @secret for the API key field, and the inline links to Stripe documentation will help developers understand the options.

Also applies to: 69-684

api/v3/api.gen.go (1)

1-4: Generated file - no manual review needed.

This file is auto-generated by oapi-codegen from the OpenAPI spec. Any issues here should be addressed in the source TypeSpec files rather than this generated output.

Comment on lines +42 to +47
@example(#{
type: AppCapabilityType.CollectPayments,
key: "stripe_collect_payment",
name: "Collect Payments",
description: "Stripe payments collects outstanding revenue with Stripe customer's default payment method.",
})
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.

⚠️ Potential issue | 🟡 Minor

Example text nit: “Stripe payments collects …”
Minor grammar tweak: “Stripe Payments collects …” or “Stripe Payments collects … using the customer’s default payment method.”

🤖 Prompt for AI Agents
In @api/spec/src/v3/apps/capability.tsp around lines 42 - 47, The description
string for the AppCapability example (entry with
AppCapabilityType.CollectPayments, key "stripe_collect_payment", name "Collect
Payments") has a grammar nit; update the description to "Stripe Payments
collects outstanding revenue using the customer’s default payment method." (or
"Stripe Payments collects outstanding revenue with the customer's default
payment method.") so the product name is capitalized and the phrase about the
default payment method reads clearly.

Comment on lines +1 to +4
import "../shared/index.tsp";
import "./capability.tsp";
import "./app.tsp";

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.

⚠️ Potential issue | 🟠 Major

Same circular import risk here (catalog.tspapp.tsp).
If you address it on one side, it’ll resolve here too (e.g., extract AppType/AppStatus into a third file imported by both).

🤖 Prompt for AI Agents
In @api/spec/src/v3/apps/catalog.tsp around lines 1 - 4, catalog.tsp currently
imports app.tsp creating a circular import with app.tsp; extract the shared
types (e.g., AppType and AppStatus) into a new module (for example
shared/app-types.tsp) and update both catalog.tsp and app.tsp to import those
types instead of importing each other, ensuring no runtime circular dependency
remains.

Comment on lines +33 to +61
@example(#{
type: AppType.Stripe,
name: "Stripe",
description: "Stripe interation allows you to collect payments with Stripe.",
capabilities: #[
#{
type: AppCapabilityType.CalculateTax,
key: "stripe_calculate_tax",
name: "Calculate Tax",
description: "Stripe Tax calculates tax portion of the invoices.",
},
#{
type: AppCapabilityType.InvoiceCustomers,
key: "stripe_invoice_customers",
name: "Invoice Customers",
description: "Stripe invoices customers with due amount.",
},
#{
type: AppCapabilityType.CollectPayments,
key: "stripe_collect_payments",
name: "Collect Payments",
description: "Stripe payments collects outstanding revenue with Stripe customer's default payment method.",
}
],
install_methods: #[
AppInstallMethods.WithOAuth2,
AppInstallMethods.WithAPIKey
],
})
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.

⚠️ Potential issue | 🟡 Minor

Fix example typo: “Stripe interation” → “Stripe integration”.
Also: your capability key examples alternate between stripe_collect_payment / stripe_collect_payments; not a blocker, but consistency helps copy/paste users.

🤖 Prompt for AI Agents
In @api/spec/src/v3/apps/catalog.tsp around lines 33 - 61, In the example block
for AppType.Stripe update the description string on the AppType.Stripe example
from "Stripe interation allows you to collect payments with Stripe." to "Stripe
integration allows you to collect payments with Stripe." and make capability
keys consistent (use a single form such as "stripe_collect_payments" for the
CollectPayments capability) by updating the key value in the corresponding
capability object; locate these in the example for AppType.Stripe where
capabilities are defined and adjust the description and the CollectPayments
capability.key to the chosen consistent name.

Comment on lines +35 to +42
@get
@route("/{type}/install/oauth2")
@operationId("get-app-catalog-item-oauth2-install-url")
@summary("Get OAuth2 install URL for an app catalog item")
getOAuth2InstallURL(
@path type: AppType,
): Shared.GetResponse<AppCatalogItem> | Common.NotFound | Common.ErrorResponses;
}
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.

⚠️ Potential issue | 🟠 Major

Return type mismatch for OAuth2 install URL endpoint.

The getOAuth2InstallURL operation returns Shared.GetResponse<AppCatalogItem>, but based on the AppOAuth2InstallResponse model defined in oauth.tsp, this should return the OAuth2 install URL response instead. This looks like it might've been copied from the get operation above.

Suggested fix
   @get
   @route("/{type}/install/oauth2")
   @operationId("get-app-catalog-item-oauth2-install-url")
   @summary("Get OAuth2 install URL for an app catalog item")
   getOAuth2InstallURL(
     @path type: AppType,
-  ): Shared.GetResponse<AppCatalogItem> | Common.NotFound | Common.ErrorResponses;
+  ): AppOAuth2InstallResponse | Common.NotFound | Common.ErrorResponses;
🤖 Prompt for AI Agents
In @api/spec/src/v3/apps/operations.tsp around lines 35 - 42, The
getOAuth2InstallURL operation currently declares its return as
Shared.GetResponse<AppCatalogItem> but should return the OAuth2 install URL
shape; update the return type to Shared.GetResponse<AppOAuth2InstallResponse>
(preserving the existing Common.NotFound | Common.ErrorResponses union) so the
endpoint returns the AppOAuth2InstallResponse model defined in oauth.tsp; locate
and modify the method declaration for getOAuth2InstallURL to use
AppOAuth2InstallResponse in the Shared.GetResponse wrapper.

Comment on lines +14 to +16
GetAppCatalogItemOauth2InstallUrlRequest = api.BillingAppType
GetAppCatalogItemOauth2InstallUrlResponse = api.BillingAppCatalogItem
GetAppCatalogItemOauth2InstallUrlHandler httptransport.HandlerWithArgs[GetAppCatalogItemOauth2InstallUrlRequest, GetAppCatalogItemOauth2InstallUrlResponse, GetAppCatalogItemOauth2InstallUrlRequest]
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the actual file and examine it
find . -name "get_app_catalog_item_oauth2_install_url.go" -type f

Repository: openmeterio/openmeter

Length of output: 130


🏁 Script executed:

#!/bin/bash
# Search for OAuth2 URL related types across the codebase
rg "OAuth2.*Url|InstallUrl|oauth2.*url" -i --type go | head -50

Repository: openmeterio/openmeter

Length of output: 6265


🏁 Script executed:

#!/bin/bash
# Look for api.BillingAppCatalogItem definition
rg "type BillingAppCatalogItem" -A 10 --type go

Repository: openmeterio/openmeter

Length of output: 634


🏁 Script executed:

#!/bin/bash
# Search for similar OAuth2 endpoints and how they define responses
rg "Oauth2|OAuth2" --type go | grep -i "response\|request" | head -20

Repository: openmeterio/openmeter

Length of output: 3024


🏁 Script executed:

#!/bin/bash
# Find the GetOauth2InstallURLOutput type definition
rg "type GetOauth2InstallURLOutput" -A 5 --type go

Repository: openmeterio/openmeter

Length of output: 357


🏁 Script executed:

#!/bin/bash
# Also check if there's a v3 equivalent or what the API spec says
rg "GetAppCatalogItemOauth2InstallUrl" -B 5 -A 15 --type go | head -100

Repository: openmeterio/openmeter

Length of output: 6075


🏁 Script executed:

#!/bin/bash
# Look at the actual handler file content to see the implementation
cat -n api/v3/handlers/apps/get_app_catalog_item_oauth2_install_url.go

Repository: openmeterio/openmeter

Length of output: 1762


Response type should return a URL, not a full catalog item.

The response type api.BillingAppCatalogItem doesn't match what an OAuth2 install URL endpoint should return. Looking at the v1 endpoint (in openmeter/app/marketplace.go), there's a GetOauth2InstallURLOutput that has just a URL field—that's the right pattern here.

Since this is currently a stub, you'll want to define a proper response type (e.g., something like BillingAppOauth2InstallUrlResponse with an installUrl field) that aligns with what the endpoint should actually return.

🤖 Prompt for AI Agents
In @api/v3/handlers/apps/get_app_catalog_item_oauth2_install_url.go around lines
14 - 16, The response alias is wrong: replace
GetAppCatalogItemOauth2InstallUrlResponse = api.BillingAppCatalogItem with a new
simple response type that returns only the install URL (e.g., define
api.BillingAppOauth2InstallUrlResponse struct with a single string field like
InstallUrl or Url) and update the handler typedef
GetAppCatalogItemOauth2InstallUrlHandler to use that new response type; add the
new type into the api package and ensure any code that constructs or returns the
response uses the InstallUrl field.

Comment on lines +14 to +46
type (
HandleStripeWebhookRequest struct {
AppId api.ULID
Body api.BillingAppStripeWebhookEvent
}
HandleStripeWebhookResponse = *struct{}
HandleStripeWebhookParams = api.ULID
HandleStripeWebhookHandler httptransport.HandlerWithArgs[HandleStripeWebhookRequest, HandleStripeWebhookResponse, HandleStripeWebhookParams]
)

func (h *handler) HandleStripeWebhook() HandleStripeWebhookHandler {
return httptransport.NewHandlerWithArgs(
func(ctx context.Context, r *http.Request, appId HandleStripeWebhookParams) (HandleStripeWebhookRequest, error) {
body := api.BillingAppStripeWebhookEvent{}
if err := request.ParseBody(r, &body); err != nil {
return HandleStripeWebhookRequest{}, err
}

return HandleStripeWebhookRequest{
AppId: appId,
Body: body,
}, nil
},
func(ctx context.Context, request HandleStripeWebhookRequest) (HandleStripeWebhookResponse, error) {
return nil, apierrors.NewNotImplementedError(ctx, nil)
},
commonhttp.EmptyResponseEncoder[HandleStripeWebhookResponse](http.StatusNoContent),
httptransport.AppendOptions(
h.options,
httptransport.WithOperationName("handle-stripe-webhook"),
httptransport.WithErrorEncoder(apierrors.GenericErrorEncoder()),
)...,
)
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the request.ParseBody function
fd -e go | head -20

Repository: openmeterio/openmeter

Length of output: 768


🏁 Script executed:

# Search for ParseBody implementation
rg "func.*ParseBody" --type go -A 5

Repository: openmeterio/openmeter

Length of output: 479


🏁 Script executed:

# Look for existing Stripe-related code and patterns
rg "Stripe" --type go -i

Repository: openmeterio/openmeter

Length of output: 50378


🏁 Script executed:

# Check the api types for BillingAppStripeWebhookEvent
rg "BillingAppStripeWebhookEvent" --type go -B 2 -A 5

Repository: openmeterio/openmeter

Length of output: 2636


🏁 Script executed:

# Look for any existing webhook signature verification patterns
rg "signature|Signature" --type go -i -B 2 -A 2 | head -50

Repository: openmeterio/openmeter

Length of output: 3859


🏁 Script executed:

# Check the Stripe webhook driver implementation
cat api/v3/request/body.go

Repository: openmeterio/openmeter

Length of output: 572


🏁 Script executed:

# Look at existing Stripe webhook handler
cat openmeter/app/stripe/httpdriver/webhook.go

Repository: openmeterio/openmeter

Length of output: 17135


🏁 Script executed:

# Search for any body preservation patterns in the codebase
rg "io.ReadAll\|TeeReader\|DumpBody" --type go -B 2 -A 2

Repository: openmeterio/openmeter

Length of output: 47


🏁 Script executed:

# Look for any middleware that reads the body
rg "ioutil.ReadAll\|io.NopCloser" --type go -B 2 -A 2 | head -50

Repository: openmeterio/openmeter

Length of output: 47


🏁 Script executed:

# Check if there are any other webhook handlers using similar patterns
rg "HandleStripeWebhook" --type go -B 5 -A 10

Repository: openmeterio/openmeter

Length of output: 10907


🏁 Script executed:

# Look at the route/registration for the new handler
rg "handle_stripe_webhook|HandleStripeWebhook" --type go -l | xargs grep -l "Route\|register" 2>/dev/null | head -5

Repository: openmeterio/openmeter

Length of output: 82


Thread the raw payload and Stripe-Signature header now to dodge refactoring this later.

The current shape won't let you verify webhook signatures once you implement the logic. Even though it's a scaffold, I'd suggest capturing:

  • The raw request payload bytes (for webhook.ConstructEventWithTolerance)
  • The Stripe-Signature header value

Your existing Stripe webhook handler in openmeter/app/stripe/httpdriver/webhook.go shows the right pattern—grab these before parsing, so you have what you need when it's time to verify.

🤖 Prompt for AI Agents
In @api/v3/handlers/apps/handle_stripe_webhook.go around lines 14 - 46, Update
HandleStripeWebhookRequest to include RawPayload []byte and StripeSignature
string and populate them in the request factory before parsing the JSON body:
read the raw request body bytes (io.ReadAll) into RawPayload, capture the
Stripe-Signature header via r.Header.Get("Stripe-Signature"), then parse the
JSON into Body from those raw bytes (e.g., json.Unmarshal or io.NopCloser +
bytes.NewReader) so signature verification can later use RawPayload and
StripeSignature while preserving the existing Body field
(api.BillingAppStripeWebhookEvent) and existing handler names
(HandleStripeWebhookRequest, HandleStripeWebhook).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants