From ab0888c107a0ae5057d08090377b158a91a9ec4b Mon Sep 17 00:00:00 2001 From: Yahan Xing Date: Thu, 9 Jul 2026 10:05:02 -0700 Subject: [PATCH 1/2] POC: Unify --events and --thin-events into a single flag This is a v2 breaking change that removes the separate --thin-events, --forward-thin-to, and --forward-thin-connect-to flags. Instead, the unified --events flag accepts both snapshot event types (e.g. charge.captured) and thin event types (e.g. v1.billing.meter.no_meter_found), auto-detecting the payload style from the event name pattern (v[0-9]+. prefix). The --forward-to flag now forwards both snapshot and thin events to the same destination. Internally, events are still split into two channels (webhooks + v2_events) for the proxy layer. Key behavior changes: - `stripe listen` with default --events "*" now opens BOTH websocket channels (snapshot + thin) - `--forward-to` forwards both payload styles to the same URL - `--forward-connect-to` forwards both payload styles for connect events Co-Authored-By: Claude Opus 4.6 Committed-By-Agent: claude --- pkg/cmd/listen.go | 72 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/pkg/cmd/listen.go b/pkg/cmd/listen.go index 665264243..0a6ffb9b0 100644 --- a/pkg/cmd/listen.go +++ b/pkg/cmd/listen.go @@ -6,6 +6,7 @@ import ( "net/url" "os" "os/signal" + "regexp" "strings" "syscall" "time" @@ -23,6 +24,8 @@ import ( "github.com/stripe/stripe-cli/pkg/websocket" ) +var thinEventPattern = regexp.MustCompile(`^v\d+\.`) + const ( webhooksWebSocketFeature = "webhooks" destinationsWebSocketFeature = "v2_events" @@ -34,13 +37,10 @@ type listenCmd struct { cmd *cobra.Command forwardURL string - forwardThinURL string forwardHeaders []string forwardConnectHeaders []string forwardConnectURL string - forwardThinConnectURL string events []string - thinEvents []string latestAPIVersion bool livemode bool useConfiguredWebhooks bool @@ -69,8 +69,8 @@ Stripe account.`, Example: `stripe listen stripe listen --events charge.captured,charge.updated \ --forward-to localhost:3000/events - stripe listen --thin-events v1.billing.meter.no_meter_found \ - --forward-thin-to localhost:3000/thin-events`, + stripe listen --events v1.billing.meter.no_meter_found \ + --forward-to localhost:3000/events`, Annotations: map[string]string{ AIAgentHelpAnnotationKey: " Use `--forward-to` to specify where events are sent, e.g. localhost:4242/webhook.\n" + " Use `--events` to filter to specific event types, e.g. `--events checkout.session.completed`.\n" + @@ -81,13 +81,10 @@ Stripe account.`, } lc.cmd.Flags().StringSliceVar(&lc.forwardConnectHeaders, "connect-headers", []string{}, "A comma-separated list of custom headers to forward for Connect. Ex: \"Key1:Value1, Key2:Value2\"") - lc.cmd.Flags().StringSliceVarP(&lc.events, "events", "e", []string{"*"}, "A comma-separated list of specific events to listen for. For a list of all possible events, see: https://stripe.com/docs/api/events/types") - lc.cmd.Flags().StringVarP(&lc.forwardURL, "forward-to", "f", "", "The URL to forward webhook events to") + lc.cmd.Flags().StringSliceVarP(&lc.events, "events", "e", []string{"*"}, "A comma-separated list of specific events to listen for. Supports both snapshot events (e.g. charge.captured) and thin events (e.g. v1.billing.meter.no_meter_found)") + lc.cmd.Flags().StringVarP(&lc.forwardURL, "forward-to", "f", "", "The URL to forward events to") lc.cmd.Flags().StringSliceVarP(&lc.forwardHeaders, "headers", "H", []string{}, "A comma-separated list of custom headers to forward. Ex: \"Key1:Value1, Key2:Value2\"") - lc.cmd.Flags().StringVarP(&lc.forwardConnectURL, "forward-connect-to", "c", "", "The URL to forward Connect webhook events to (default: same as normal events)") - lc.cmd.Flags().StringSliceVar(&lc.thinEvents, "thin-events", []string{}, "A comma-separated list of thin events to listen for.") - lc.cmd.Flags().StringVar(&lc.forwardThinURL, "forward-thin-to", "", "The URL to forward thin events to") - lc.cmd.Flags().StringVar(&lc.forwardThinConnectURL, "forward-thin-connect-to", "", "The URL to forward thin Connect events to") + lc.cmd.Flags().StringVarP(&lc.forwardConnectURL, "forward-connect-to", "c", "", "The URL to forward Connect events to (default: same as normal events)") lc.cmd.Flags().BoolVarP(&lc.latestAPIVersion, "latest", "l", false, "Receive events formatted with the latest API version (default: your account's default API version)") lc.cmd.Flags().BoolVar(&lc.livemode, "live", false, "Receive live events (default: test)") lc.cmd.Flags().BoolVarP(&lc.printJSON, "print-json", "j", false, "Print full JSON objects to stdout.") @@ -179,15 +176,17 @@ func (lc *listenCmd) runListenCmd(cmd *cobra.Command, args []string) error { proxyVisitor := lc.createVisitor(logger, lc.format, lc.printJSON) proxyOutCh := make(chan websocket.IElement) + snapshotEvents, thinEvents := lc.splitEventsByType() + p, err := proxy.Init(ctx, &proxy.Config{ Client: client, DeviceName: deviceName, DeviceToken: &lc.deviceToken, ForwardURL: lc.forwardURL, - ForwardThinURL: lc.forwardThinURL, + ForwardThinURL: lc.forwardURL, ForwardHeaders: lc.forwardHeaders, ForwardConnectURL: lc.forwardConnectURL, - ForwardThinConnectURL: lc.forwardThinConnectURL, + ForwardThinConnectURL: lc.forwardConnectURL, ForwardConnectHeaders: lc.forwardConnectHeaders, UseConfiguredWebhooks: lc.useConfiguredWebhooks, WebSocketFeatures: lc.getFeatures(), @@ -197,8 +196,8 @@ func (lc *listenCmd) runListenCmd(cmd *cobra.Command, args []string) error { Log: logger, NoWSS: lc.noWSS, Timeout: lc.timeout, - Events: lc.events, - ThinEvents: lc.thinEvents, + Events: snapshotEvents, + ThinEvents: thinEvents, OutCh: proxyOutCh, LoggedInAccountID: accountID, }) @@ -359,15 +358,48 @@ func (lc *listenCmd) createVisitor(logger *log.Logger, format string, printJSON } func (lc *listenCmd) getFeatures() []string { - features := []string{} + needsSnapshot := false + needsThin := false + + for _, e := range lc.events { + if e == "*" { + needsSnapshot = true + needsThin = true + break + } + if isThinEvent(e) { + needsThin = true + } else { + needsSnapshot = true + } + } - if len(lc.events) > 0 { + features := []string{} + if needsSnapshot { features = append(features, webhooksWebSocketFeature) } - - if len(lc.thinEvents) > 0 { + if needsThin { features = append(features, destinationsWebSocketFeature) } - return features } + +// splitEventsByType separates the unified --events list into snapshot and thin +// event lists for the proxy, which still uses two separate channels internally. +func (lc *listenCmd) splitEventsByType() (snapshotEvents []string, thinEvents []string) { + for _, e := range lc.events { + if e == "*" { + snapshotEvents = append(snapshotEvents, "*") + thinEvents = append(thinEvents, "*") + } else if isThinEvent(e) { + thinEvents = append(thinEvents, e) + } else { + snapshotEvents = append(snapshotEvents, e) + } + } + return +} + +func isThinEvent(eventType string) bool { + return thinEventPattern.MatchString(eventType) +} From 40d48302c02da39da5e4823914bdbfd9804f7032 Mon Sep 17 00:00:00 2001 From: Yahan Xing Date: Thu, 9 Jul 2026 15:40:35 -0700 Subject: [PATCH 2/2] Fix gocritic lint: rewrite if-else chain as switch Co-Authored-By: Claude Opus 4.6 Committed-By-Agent: claude --- pkg/cmd/listen.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/listen.go b/pkg/cmd/listen.go index 0a6ffb9b0..31d7e978d 100644 --- a/pkg/cmd/listen.go +++ b/pkg/cmd/listen.go @@ -388,12 +388,13 @@ func (lc *listenCmd) getFeatures() []string { // event lists for the proxy, which still uses two separate channels internally. func (lc *listenCmd) splitEventsByType() (snapshotEvents []string, thinEvents []string) { for _, e := range lc.events { - if e == "*" { + switch { + case e == "*": snapshotEvents = append(snapshotEvents, "*") thinEvents = append(thinEvents, "*") - } else if isThinEvent(e) { + case isThinEvent(e): thinEvents = append(thinEvents, e) - } else { + default: snapshotEvents = append(snapshotEvents, e) } }