From a317a09c3c9b1cceeda018df3af9d9831fe98b42 Mon Sep 17 00:00:00 2001 From: Arthur Moreira de Deus Date: Thu, 2 Apr 2026 11:39:48 -0300 Subject: [PATCH 1/4] Update index.tsx --- src/components/CodeBlock/index.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/CodeBlock/index.tsx b/src/components/CodeBlock/index.tsx index 4674039e84b3..6b1702cd7633 100644 --- a/src/components/CodeBlock/index.tsx +++ b/src/components/CodeBlock/index.tsx @@ -146,7 +146,9 @@ export const SingleCodeBlock = ({ label, language, children, ...props }: SingleC const tooltipKey = '// TIP:' const highlightKey = '// HIGHLIGHT' const diffAddKey = '// +' +const diffAddKeyHash = '# +' const diffRemoveKey = '// -' +const diffRemoveKeyHash = '# -' const removeQuotes = (str?: string | null): string | null | undefined => { return str?.replace(/['"]/g, '') @@ -157,7 +159,9 @@ const stripAnnotationComments = (code: string): string => { .replace(tooltipKey, '//') .replace(highlightKey, '') .replace(diffAddKey, '') + .replace(diffAddKeyHash, '') .replace(diffRemoveKey, '') + .replace(diffRemoveKeyHash, '') .trim() } @@ -271,10 +275,10 @@ export const CodeBlock = ({ if (line.includes(highlightKey)) { highlightLineNumbers.push(index) } - if (line.includes(diffAddKey)) { + if (line.includes(diffAddKey) || line.includes(diffAddKeyHash)) { diffAddLineNumbers.push(index) } - if (line.includes(diffRemoveKey)) { + if (line.includes(diffRemoveKey) || line.includes(diffRemoveKeyHash)) { diffRemoveLineNumbers.push(index) } }) @@ -581,9 +585,15 @@ export const CodeBlock = ({ if (token.content.includes(diffAddKey)) { token.content = token.content.replace(diffAddKey, '') } + if (token.content.includes(diffAddKeyHash)) { + token.content = token.content.replace(diffAddKeyHash, '') + } if (token.content.includes(diffRemoveKey)) { token.content = token.content.replace(diffRemoveKey, '') } + if (token.content.includes(diffRemoveKeyHash)) { + token.content = token.content.replace(diffRemoveKeyHash, '') + } }) const firstContentIndex = line.findIndex( From 7e02dcc50bf5381057d3f9f4796eb60718d854af Mon Sep 17 00:00:00 2001 From: Arthur Moreira de Deus Date: Thu, 2 Apr 2026 11:40:49 -0300 Subject: [PATCH 2/4] docs: Add instructions to send distinct id as metadata to Stripe --- .../connect-to-customers.mdx | 233 +++++++++++++++++- 1 file changed, 223 insertions(+), 10 deletions(-) diff --git a/contents/docs/revenue-analytics/connect-to-customers.mdx b/contents/docs/revenue-analytics/connect-to-customers.mdx index b92eaef97215..bd814c8090ae 100644 --- a/contents/docs/revenue-analytics/connect-to-customers.mdx +++ b/contents/docs/revenue-analytics/connect-to-customers.mdx @@ -8,24 +8,237 @@ import RevAnalyticsBetaWarning from './_snippets/rev-analytics-beta-warning.mdx' -You can connect your revenue data to `persons` and `groups` in the [revenue analytics settings](https://app.posthog.com/data-management/revenue). +The connection between your revenue data and `persons` and `groups` is automatically done when you're using revenue events (since we know what person/group an event belongs to) but we need your help to map them in case you're using a data warehouse source. -This is automatically done when you're using revenue events (since we know what person/group an event belongs to) but we need your help to manually map them in case you're using a data warehouse source. +## Step 1: Add metadata when creating new customers - +Search your codebase for where you create Stripe customers (e.g. `stripe.Customer.create` or equivalent) and add the `posthog_person_distinct_id` metadata field. + + + +```python +customer = stripe.Customer.create( + email=user.email, + metadata={"posthog_person_distinct_id": user.posthog_distinct_id}, # + +) +``` + +```node +const customer = await stripe.customers.create({ + email: user.email, + metadata: { posthog_person_distinct_id: user.posthogDistinctId }, // + +}); +``` + +```ruby +customer = Stripe::Customer.create({ + email: user.email, + metadata: { posthog_person_distinct_id: user.posthog_distinct_id }, # + +}) +``` + +```php +$customer = $stripe->customers->create([ + 'email' => $user->email, + 'metadata' => ['posthog_person_distinct_id' => $user->posthogDistinctId], // + +]); +``` + +```go +params := &stripe.CustomerParams{ + Email: stripe.String(user.Email), +} +params.AddMetadata("posthog_person_distinct_id", user.PosthogDistinctID) // + +cust, err := customer.New(params) +``` + +```java +CustomerCreateParams params = CustomerCreateParams.builder() + .setEmail(user.getEmail()) + .putMetadata("posthog_person_distinct_id", user.getPosthogDistinctId()) // + + .build(); +Customer customer = Customer.create(params); +``` + +```dotnet +var options = new CustomerCreateOptions +{ + Email = user.Email, + Metadata = new Dictionary // + + { // + + { "posthog_person_distinct_id", user.PosthogDistinctId }, // + + }, // + +}; +var customer = await customerService.CreateAsync(options); +``` + + + +## Step 2: Update existing customers before charges + +For customers created before you added the metadata, add a `Customer.modify` call before your charge or checkout code. This ensures existing customers get tagged the next time they make a payment. + +Search your codebase for any of these patterns: +- `PaymentIntent.create` — one-off charges +- `Subscription.create` — new subscriptions +- `checkout.Session.create` — Stripe Checkout +- `Invoice.create` — manual invoices + +Then add the following **before** that code: + + + +```python +stripe.Customer.modify( + user.stripe_customer_id, + metadata={"posthog_person_distinct_id": user.posthog_distinct_id}, +) +``` + +```node +await stripe.customers.update( + user.stripeCustomerId, + { metadata: { posthog_person_distinct_id: user.posthogDistinctId } } +); +``` + +```ruby +Stripe::Customer.update( + user.stripe_customer_id, + { metadata: { posthog_person_distinct_id: user.posthog_distinct_id } } +) +``` + +```php +$stripe->customers->update( + $user->stripeCustomerId, + ['metadata' => ['posthog_person_distinct_id' => $user->posthogDistinctId]] +); +``` + +```go +params := &stripe.CustomerParams{} +params.AddMetadata("posthog_person_distinct_id", user.PosthogDistinctID) +_, err := customer.Update(user.StripeCustomerID, params) +``` + +```java +CustomerUpdateParams params = CustomerUpdateParams.builder() + .putMetadata("posthog_person_distinct_id", user.getPosthogDistinctId()) + .build(); +Customer.retrieve(user.getStripeCustomerId()).update(params); +``` + +```dotnet +var options = new CustomerUpdateOptions +{ + Metadata = new Dictionary + { + { "posthog_person_distinct_id", user.PosthogDistinctId }, + }, +}; +await customerService.UpdateAsync(user.StripeCustomerId, options); +``` + + + +> **Using Stripe Checkout?** If you use `checkout.Session.create` and let Stripe create customers automatically, you won't have a `Customer.create` call in your code. Instead, handle this in your `checkout.session.completed` webhook: + + + +```python +# In your checkout.session.completed webhook handler +session = event.data.object +user = get_user_by_client_reference_id(session.client_reference_id) + +stripe.Customer.modify( + session.customer, + metadata={"posthog_person_distinct_id": user.posthog_distinct_id}, +) +``` + +```node +// In your checkout.session.completed webhook handler +const session = event.data.object; +const user = await getUserByClientReferenceId(session.client_reference_id); + +await stripe.customers.update( + session.customer, + { metadata: { posthog_person_distinct_id: user.posthogDistinctId } } +); +``` + +```ruby +# In your checkout.session.completed webhook handler +session = event.data.object +user = get_user_by_client_reference_id(session.client_reference_id) + +Stripe::Customer.update( + session.customer, + { metadata: { posthog_person_distinct_id: user.posthog_distinct_id } } +) +``` + +```php +// In your checkout.session.completed webhook handler +$session = $event->data->object; +$user = getUserByClientReferenceId($session->client_reference_id); + +$stripe->customers->update( + $session->customer, + ['metadata' => ['posthog_person_distinct_id' => $user->posthogDistinctId]] +); +``` + +```go +// In your checkout.session.completed webhook handler +var session stripe.CheckoutSession +err := json.Unmarshal(event.Data.Raw, &session) + +user := getUserByClientReferenceID(session.ClientReferenceID) + +params := &stripe.CustomerParams{} +params.AddMetadata("posthog_person_distinct_id", user.PosthogDistinctID) +_, err = customer.Update(session.Customer.ID, params) +``` + +```java +// In your checkout.session.completed webhook handler +Session session = (Session) event.getData().getObject(); +User user = getUserByClientReferenceId(session.getClientReferenceId()); + +CustomerUpdateParams params = CustomerUpdateParams.builder() + .putMetadata("posthog_person_distinct_id", user.getPosthogDistinctId()) + .build(); +Customer.retrieve(session.getCustomer()).update(params); +``` + +```dotnet +// In your checkout.session.completed webhook handler +var session = event.Data.Object as Session; +var user = await GetUserByClientReferenceId(session.ClientReferenceId); + +var options = new CustomerUpdateOptions +{ + Metadata = new Dictionary + { + { "posthog_person_distinct_id", user.PosthogDistinctId }, + }, +}; +await customerService.UpdateAsync(session.Customer, options); +``` + + + +> **Tip:** Set `client_reference_id` to your internal user ID when creating Checkout Sessions. This lets you look up the user in your database when the webhook fires. Once this is connected you'll be able to properly see who your top customers are in the [Top customers dashboard](https://app.posthog.com/revenue_analytics#top-customers). -You'll also get access to the `persons_revenue_analytics` and `groups_revenue_analytics` tables in the [data warehouse](https://app.posthog.com/data-warehouse). This is a simple map of `person_id`/`group_key` to what their all-time revenue is. We plan on expanding that soon with more fields and also making that data available on the soon-to-be-released [CRM](/teams/crm) page. +You'll also get access to the `persons_revenue_analytics` and `groups_revenue_analytics` tables in the [data warehouse](https://app.posthog.com/data-warehouse). This is a simple map of `person_id`/`group_key` to what their all-time revenue is. ```sql -- Count the number of persons with revenue greater than 1,000,000 SELECT COUNT(*) FROM persons_revenue_analytics WHERE amount > 1000000 -``` \ No newline at end of file +``` From d4f979175bcf32ca566fd0f9328e7633440d7fb4 Mon Sep 17 00:00:00 2001 From: Arthur Moreira de Deus Date: Fri, 3 Apr 2026 09:14:30 -0300 Subject: [PATCH 3/4] docs: Add wizard command to configure revenue analytics --- .../connect-to-customers.mdx | 361 +++++++++++++----- src/components/WizardCommand/index.tsx | 6 +- 2 files changed, 280 insertions(+), 87 deletions(-) diff --git a/contents/docs/revenue-analytics/connect-to-customers.mdx b/contents/docs/revenue-analytics/connect-to-customers.mdx index bd814c8090ae..e474b78ae9d6 100644 --- a/contents/docs/revenue-analytics/connect-to-customers.mdx +++ b/contents/docs/revenue-analytics/connect-to-customers.mdx @@ -5,10 +5,19 @@ showTitle: true --- import RevAnalyticsBetaWarning from './_snippets/rev-analytics-beta-warning.mdx' +import WizardCommand from 'components/WizardCommand' -The connection between your revenue data and `persons` and `groups` is automatically done when you're using revenue events (since we know what person/group an event belongs to) but we need your help to map them in case you're using a data warehouse source. +PostHog automatically connects revenue data to persons and groups when you use revenue events. For data warehouse sources, you need to map the connection manually. + +## The easy way + +Let the wizard handle the metadata configuration for you by running this command in your project directory with your terminal: + + + + ## Step 1: Add metadata when creating new customers @@ -74,163 +83,343 @@ var customer = await customerService.CreateAsync(options); -## Step 2: Update existing customers before charges +## Step 2: Tag existing customers via charges, subscriptions, or invoices -For customers created before you added the metadata, add a `Customer.modify` call before your charge or checkout code. This ensures existing customers get tagged the next time they make a payment. +For customers created before you added the metadata in step 1, you don't need to update the customer object directly. Instead, pass `posthog_person_distinct_id` as metadata on any charge, subscription, or invoice tied to that customer. PostHog automatically resolves it from the most recently created child object. -Search your codebase for any of these patterns: -- `PaymentIntent.create` — one-off charges -- `Subscription.create` — new subscriptions -- `checkout.Session.create` — Stripe Checkout -- `Invoice.create` — manual invoices +Add the metadata to whichever Stripe call you already make. Here are the most common patterns: -Then add the following **before** that code: +### Subscriptions ```python -stripe.Customer.modify( - user.stripe_customer_id, - metadata={"posthog_person_distinct_id": user.posthog_distinct_id}, +stripe.Subscription.create( + customer=user.stripe_customer_id, + items=[{"price": "price_xxx"}], + metadata={"posthog_person_distinct_id": user.posthog_distinct_id}, # + ) ``` ```node -await stripe.customers.update( - user.stripeCustomerId, - { metadata: { posthog_person_distinct_id: user.posthogDistinctId } } -); +await stripe.subscriptions.create({ + customer: user.stripeCustomerId, + items: [{ price: 'price_xxx' }], + metadata: { posthog_person_distinct_id: user.posthogDistinctId }, // + +}); ``` ```ruby -Stripe::Customer.update( - user.stripe_customer_id, - { metadata: { posthog_person_distinct_id: user.posthog_distinct_id } } -) +Stripe::Subscription.create({ + customer: user.stripe_customer_id, + items: [{ price: 'price_xxx' }], + metadata: { posthog_person_distinct_id: user.posthog_distinct_id }, # + +}) ``` ```php -$stripe->customers->update( - $user->stripeCustomerId, - ['metadata' => ['posthog_person_distinct_id' => $user->posthogDistinctId]] -); +$stripe->subscriptions->create([ + 'customer' => $user->stripeCustomerId, + 'items' => [['price' => 'price_xxx']], + 'metadata' => ['posthog_person_distinct_id' => $user->posthogDistinctId], // + +]); ``` ```go -params := &stripe.CustomerParams{} -params.AddMetadata("posthog_person_distinct_id", user.PosthogDistinctID) -_, err := customer.Update(user.StripeCustomerID, params) +params := &stripe.SubscriptionParams{ + Customer: stripe.String(user.StripeCustomerID), + Items: []*stripe.SubscriptionItemsParams{ + {Price: stripe.String("price_xxx")}, + }, +} +params.AddMetadata("posthog_person_distinct_id", user.PosthogDistinctID) // + +sub, err := subscription.New(params) ``` ```java -CustomerUpdateParams params = CustomerUpdateParams.builder() - .putMetadata("posthog_person_distinct_id", user.getPosthogDistinctId()) +SubscriptionCreateParams params = SubscriptionCreateParams.builder() + .setCustomer(user.getStripeCustomerId()) + .addItem(SubscriptionCreateParams.Item.builder().setPrice("price_xxx").build()) + .putMetadata("posthog_person_distinct_id", user.getPosthogDistinctId()) // + .build(); -Customer.retrieve(user.getStripeCustomerId()).update(params); +Subscription subscription = Subscription.create(params); ``` ```dotnet -var options = new CustomerUpdateOptions +var options = new SubscriptionCreateOptions { - Metadata = new Dictionary + Customer = user.StripeCustomerId, + Items = new List { - { "posthog_person_distinct_id", user.PosthogDistinctId }, + new() { Price = "price_xxx" }, }, + Metadata = new Dictionary // + + { // + + { "posthog_person_distinct_id", user.PosthogDistinctId }, // + + }, // + }; -await customerService.UpdateAsync(user.StripeCustomerId, options); +var subscription = await subscriptionService.CreateAsync(options); ``` -> **Using Stripe Checkout?** If you use `checkout.Session.create` and let Stripe create customers automatically, you won't have a `Customer.create` call in your code. Instead, handle this in your `checkout.session.completed` webhook: +### One-off charges (payment intents) ```python -# In your checkout.session.completed webhook handler -session = event.data.object -user = get_user_by_client_reference_id(session.client_reference_id) +stripe.PaymentIntent.create( + amount=1000, + currency="usd", + customer=user.stripe_customer_id, + metadata={"posthog_person_distinct_id": user.posthog_distinct_id}, # + +) +``` + +```node +await stripe.paymentIntents.create({ + amount: 1000, + currency: 'usd', + customer: user.stripeCustomerId, + metadata: { posthog_person_distinct_id: user.posthogDistinctId }, // + +}); +``` + +```ruby +Stripe::PaymentIntent.create({ + amount: 1000, + currency: 'usd', + customer: user.stripe_customer_id, + metadata: { posthog_person_distinct_id: user.posthog_distinct_id }, # + +}) +``` -stripe.Customer.modify( - session.customer, - metadata={"posthog_person_distinct_id": user.posthog_distinct_id}, +```php +$stripe->paymentIntents->create([ + 'amount' => 1000, + 'currency' => 'usd', + 'customer' => $user->stripeCustomerId, + 'metadata' => ['posthog_person_distinct_id' => $user->posthogDistinctId], // + +]); +``` + +```go +params := &stripe.PaymentIntentParams{ + Amount: stripe.Int64(1000), + Currency: stripe.String(string(stripe.CurrencyUSD)), + Customer: stripe.String(user.StripeCustomerID), +} +params.AddMetadata("posthog_person_distinct_id", user.PosthogDistinctID) // + +pi, err := paymentintent.New(params) +``` + +```java +PaymentIntentCreateParams params = PaymentIntentCreateParams.builder() + .setAmount(1000L) + .setCurrency("usd") + .setCustomer(user.getStripeCustomerId()) + .putMetadata("posthog_person_distinct_id", user.getPosthogDistinctId()) // + + .build(); +PaymentIntent intent = PaymentIntent.create(params); +``` + +```dotnet +var options = new PaymentIntentCreateOptions +{ + Amount = 1000, + Currency = "usd", + Customer = user.StripeCustomerId, + Metadata = new Dictionary // + + { // + + { "posthog_person_distinct_id", user.PosthogDistinctId }, // + + }, // + +}; +var intent = await paymentIntentService.CreateAsync(options); +``` + + + +### Stripe Checkout + +Pass the metadata in the checkout session's `subscription_data` or `payment_intent_data` depending on your checkout mode. Also set `client_reference_id` to your internal user ID so you can look up the distinct ID. + + + +```python +# For recurring (subscription) checkout +session = stripe.checkout.Session.create( + mode="subscription", + client_reference_id=user.id, + subscription_data={ + "metadata": {"posthog_person_distinct_id": user.posthog_distinct_id}, # + + }, + # ... other params +) + +# For one-time payment checkout +session = stripe.checkout.Session.create( + mode="payment", + client_reference_id=user.id, + payment_intent_data={ + "metadata": {"posthog_person_distinct_id": user.posthog_distinct_id}, # + + }, + # ... other params ) ``` ```node -// In your checkout.session.completed webhook handler -const session = event.data.object; -const user = await getUserByClientReferenceId(session.client_reference_id); +// For recurring (subscription) checkout +const session = await stripe.checkout.sessions.create({ + mode: 'subscription', + client_reference_id: user.id, + subscription_data: { + metadata: { posthog_person_distinct_id: user.posthogDistinctId }, // + + }, + // ... other params +}); -await stripe.customers.update( - session.customer, - { metadata: { posthog_person_distinct_id: user.posthogDistinctId } } -); +// For one-time payment checkout +const session = await stripe.checkout.sessions.create({ + mode: 'payment', + client_reference_id: user.id, + payment_intent_data: { + metadata: { posthog_person_distinct_id: user.posthogDistinctId }, // + + }, + // ... other params +}); ``` ```ruby -# In your checkout.session.completed webhook handler -session = event.data.object -user = get_user_by_client_reference_id(session.client_reference_id) +# For recurring (subscription) checkout +session = Stripe::Checkout::Session.create({ + mode: 'subscription', + client_reference_id: user.id, + subscription_data: { + metadata: { posthog_person_distinct_id: user.posthog_distinct_id }, # + + }, + # ... other params +}) -Stripe::Customer.update( - session.customer, - { metadata: { posthog_person_distinct_id: user.posthog_distinct_id } } -) +# For one-time payment checkout +session = Stripe::Checkout::Session.create({ + mode: 'payment', + client_reference_id: user.id, + payment_intent_data: { + metadata: { posthog_person_distinct_id: user.posthog_distinct_id }, # + + }, + # ... other params +}) ``` ```php -// In your checkout.session.completed webhook handler -$session = $event->data->object; -$user = getUserByClientReferenceId($session->client_reference_id); +// For recurring (subscription) checkout +$session = $stripe->checkout->sessions->create([ + 'mode' => 'subscription', + 'client_reference_id' => $user->id, + 'subscription_data' => [ + 'metadata' => ['posthog_person_distinct_id' => $user->posthogDistinctId], // + + ], + // ... other params +]); -$stripe->customers->update( - $session->customer, - ['metadata' => ['posthog_person_distinct_id' => $user->posthogDistinctId]] -); +// For one-time payment checkout +$session = $stripe->checkout->sessions->create([ + 'mode' => 'payment', + 'client_reference_id' => $user->id, + 'payment_intent_data' => [ + 'metadata' => ['posthog_person_distinct_id' => $user->posthogDistinctId], // + + ], + // ... other params +]); ``` ```go -// In your checkout.session.completed webhook handler -var session stripe.CheckoutSession -err := json.Unmarshal(event.Data.Raw, &session) - -user := getUserByClientReferenceID(session.ClientReferenceID) - -params := &stripe.CustomerParams{} -params.AddMetadata("posthog_person_distinct_id", user.PosthogDistinctID) -_, err = customer.Update(session.Customer.ID, params) +// For recurring (subscription) checkout +params := &stripe.CheckoutSessionParams{ + Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)), + ClientReferenceID: stripe.String(user.ID), + SubscriptionData: &stripe.CheckoutSessionSubscriptionDataParams{ + Metadata: map[string]string{ + "posthog_person_distinct_id": user.PosthogDistinctID, // + + }, + }, +} +session, err := checkoutsession.New(params) + +// For one-time payment checkout +params := &stripe.CheckoutSessionParams{ + Mode: stripe.String(string(stripe.CheckoutSessionModePayment)), + ClientReferenceID: stripe.String(user.ID), + PaymentIntentData: &stripe.CheckoutSessionPaymentIntentDataParams{ + Metadata: map[string]string{ + "posthog_person_distinct_id": user.PosthogDistinctID, // + + }, + }, +} +session, err := checkoutsession.New(params) ``` ```java -// In your checkout.session.completed webhook handler -Session session = (Session) event.getData().getObject(); -User user = getUserByClientReferenceId(session.getClientReferenceId()); - -CustomerUpdateParams params = CustomerUpdateParams.builder() - .putMetadata("posthog_person_distinct_id", user.getPosthogDistinctId()) +// For recurring (subscription) checkout +SessionCreateParams params = SessionCreateParams.builder() + .setMode(SessionCreateParams.Mode.SUBSCRIPTION) + .setClientReferenceId(user.getId()) + .setSubscriptionData( + SessionCreateParams.SubscriptionData.builder() + .putMetadata("posthog_person_distinct_id", user.getPosthogDistinctId()) // + + .build() + ) .build(); -Customer.retrieve(session.getCustomer()).update(params); +Session session = Session.create(params); + +// For one-time payment checkout +SessionCreateParams params = SessionCreateParams.builder() + .setMode(SessionCreateParams.Mode.PAYMENT) + .setClientReferenceId(user.getId()) + .setPaymentIntentData( + SessionCreateParams.PaymentIntentData.builder() + .putMetadata("posthog_person_distinct_id", user.getPosthogDistinctId()) // + + .build() + ) + .build(); +Session session = Session.create(params); ``` ```dotnet -// In your checkout.session.completed webhook handler -var session = event.Data.Object as Session; -var user = await GetUserByClientReferenceId(session.ClientReferenceId); +// For recurring (subscription) checkout +var options = new SessionCreateOptions +{ + Mode = "subscription", + ClientReferenceId = user.Id, + SubscriptionData = new SessionSubscriptionDataOptions + { + Metadata = new Dictionary // + + { // + + { "posthog_person_distinct_id", user.PosthogDistinctId }, // + + }, // + + }, +}; +var session = await sessionService.CreateAsync(options); -var options = new CustomerUpdateOptions +// For one-time payment checkout +var options = new SessionCreateOptions { - Metadata = new Dictionary + Mode = "payment", + ClientReferenceId = user.Id, + PaymentIntentData = new SessionPaymentIntentDataOptions { - { "posthog_person_distinct_id", user.PosthogDistinctId }, + Metadata = new Dictionary // + + { // + + { "posthog_person_distinct_id", user.PosthogDistinctId }, // + + }, // + }, }; -await customerService.UpdateAsync(session.Customer, options); +var session = await sessionService.CreateAsync(options); ``` -> **Tip:** Set `client_reference_id` to your internal user ID when creating Checkout Sessions. This lets you look up the user in your database when the webhook fires. +> **How does this work?** PostHog looks for `posthog_person_distinct_id` in the metadata of subscriptions, charges, and invoices tied to each Stripe customer. If the customer object doesn't have the metadata directly, PostHog uses the value from the most recently created child object. Once this is connected you'll be able to properly see who your top customers are in the [Top customers dashboard](https://app.posthog.com/revenue_analytics#top-customers). diff --git a/src/components/WizardCommand/index.tsx b/src/components/WizardCommand/index.tsx index 54639947e42d..6f2334eadfdb 100644 --- a/src/components/WizardCommand/index.tsx +++ b/src/components/WizardCommand/index.tsx @@ -7,11 +7,13 @@ import ZoomHover from 'components/ZoomHover' export default function WizardCommand({ className = '', + command = '', latest = true, slim = false, onCopy, }: { className?: string + command?: string latest?: boolean slim?: boolean onCopy?: () => void @@ -19,7 +21,9 @@ export default function WizardCommand({ const cloud = useCloud() const { addToast } = useToast() const [copyKey, setCopyKey] = useState(0) - const code = `npx @posthog/wizard${latest ? '@latest' : ''}${cloud ? ` --region ${cloud}` : ''}` + const code = `npx @posthog/wizard${latest ? '@latest' : ''}${cloud ? ` --region ${cloud}` : ''}${ + command ? ` ${command}` : '' + }` const handleCopy = () => { navigator.clipboard.writeText(code) From e22c13ccb4b3bb0719be7e3ec2a2629eecb42ae4 Mon Sep 17 00:00:00 2001 From: Arthur Moreira de Deus Date: Thu, 9 Apr 2026 18:54:47 -0300 Subject: [PATCH 4/4] docs: Remove wizard reference (for now..) --- contents/docs/revenue-analytics/connect-to-customers.mdx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/contents/docs/revenue-analytics/connect-to-customers.mdx b/contents/docs/revenue-analytics/connect-to-customers.mdx index e474b78ae9d6..48baea62fcc7 100644 --- a/contents/docs/revenue-analytics/connect-to-customers.mdx +++ b/contents/docs/revenue-analytics/connect-to-customers.mdx @@ -11,14 +11,6 @@ import WizardCommand from 'components/WizardCommand' PostHog automatically connects revenue data to persons and groups when you use revenue events. For data warehouse sources, you need to map the connection manually. -## The easy way - -Let the wizard handle the metadata configuration for you by running this command in your project directory with your terminal: - - - - - ## Step 1: Add metadata when creating new customers Search your codebase for where you create Stripe customers (e.g. `stripe.Customer.create` or equivalent) and add the `posthog_person_distinct_id` metadata field.