Managing billing
All PostHog instances talk to a common external Billing Service. This service is the single point for managing billing across PostHog Cloud US, PostHog Cloud EU (and ,formerly, self-hosted customers).
The Billing Service is the source of truth for product information, what plans are offered on those products (eg a free vs a paid plan on Session Replay), and feature entitlements on those plans. Our payment provider Stripe is the source of truth for customer information, invoices, and payments. The billing service communicates with Stripe to pull all the relevant information together before responding to customer requests.
Stripe Products & Prices
⚠️ Modifying products and prices should be done carefully. If you aren't sure at any point contact the #growth team to check what you are doing
Each of our billable Products has an entry in Stripe with each Product having multiple Prices. We use a billing config file to determine what is shown in the UI and how billing should behave. We use very limited metadata on some of these prices to allow the Billing Service to appropriately load and offer products to the instances:
Custom metadata
On Stripe Products
posthog_product_key
:posthog_analytics
|session_replay
| ... -> This allows PostHog to find and map the relevant products. Important: There should never be more than 1 Stripe product with the sameposthog_product_key
. The list of keys is defined in the main billing config.
On Stripe Product Prices The following keys are used to manage Startup prices:
plan
- Any Startup plan prices must have theplan
metadata set tostartup
to have their subscription automatically moved to the default (paid) prices. If not, when their subscription ends they will instead be switched to the free plans for all products.valid_days
-> The number of days a price is valid for, before automatically switching to another plan (thedefault
plan unlessmove_to_price_id
is set). Useful to create pricing that is only valid for a specific period, e.g. for the startup plans. Note: if more than one price withvalid_days
is added to a subscription, the validity period will be the shortest of the two, before resetting all plans to the default onesmove_to_price_id
-> Can be used to specify if the customer needs to be moved to a specific pricing, rather than the default one, at the end of the subscription period.
Working with pricing
Each Product has multiple prices that can be used in a subscription. Which price is default depends on the billing config file.
The default
price in Stripe does not affect the actual default price for a product. This is instead defined in the billing config.
In general, if coming from the UI, a customer will subscribe to certain prices depending on the config. There are special prices named Free
which can be used to give a product for free. These can be added manually and are typically used for Enterprisey customers who pay a flat fee up-front and $0 for the actual usage (which we still want to track but not charge for).
Types of billing plans we support
We generally support the following types of billing plans:
- Standard metered
- This includes usage-based and metered, even if it has custom price tiers or is a special program like the Startup program.
- Metered, but with discount coupon
- Flat first tier, metered after
- Up-front payment, $0 first tier, metered after
- Flat up-front, no metering (renegotiate contract if they go over)
If at all possible, it's best to stay with these types of billing plans because we already support them, and adding extra stuff will increase complexity. If you do need to add a different type of billing plan, chat with the growth team before agreeing to anything with a customer to make sure it's possible!
Coupons and Discounts
As much as possible the existing prices should be used in combination with Coupons
to offer custom deals to customers. Coupons are applied to the Customer in Stripe, not to the customer's subscription.
- Visit the customer in the Stripe dashboard.
- Select Actions -> Apply Coupon.
- Select the coupon to apply.
- The UI should soon reflect the change. If you need it to reflect immediately, use the "Sync selected customers with Stripe" action in Django Admin.
When calculating usage limits, discounts are taken into consideration before the limit is calculated. This means that if the customer sets a billing limit of $200 and has a 20% discount, they will get charged $200 for $250 worth of volume.
Bespoke prices
In more complex cases it may be useful to create a custom pricing configuration for a product(s). To do this:
- Go to the appropriate product in question (do not create your own Product)
- Click "Add another price"
- Important: For metered products (e.g. Product Analytics, Session Replay) select
Graduated Pricing
,Usage is metered
andMaximum usage during period
. This is crucial as the Billing Service will always send the maximum number of events for the billable period, respecting any billing limits set at the time. - Expand the
additional options
and add a straightforward Price Description likeCustom - {date of creation}
- Add the tiers as you see fit
- If the custom prices are for a product and addons (eg. Product analytics and Group analytics) the tier volumes need to be exactly the same between the two products/prices. If tier 3 for Product analytics is up to 15M and tier 3 for Group analytics is for 16M, you'll get errors from the billing service).
- If you are making a custom price for just one product (ie. someone is getting special pricing for Product Analytics but will get the normal pricing for Group Analytics), make sure the tiers match up between the main product and the addons.
- Add custom metadata if needed.
Plans
⚠️ Modifying plans should be done carefully. If you aren't sure at any point contact the #growth team to check what you are doing
You can find a list of available plans in the billing repo. These are found inside costants/plans
, divided by folder.
Each plan can have a list of features, and a price.
Features are used to infer which features are available in the product, for a customer on that plan.
You can manually change the plan for a customer by updating the plans_map
in the billing admin panel.
Giving customers a free trial
- Find the Organization in the Billing Service Admin portal
- Find the
Free Trial Until
field and update that to the appropriate date - The next time that Customer visits PostHog, their
AvailableFeatures
will be updated to reflect the standard premium features (they might have to refresh their page to properly sync the new billing information). - Once this date passes their
AvailableFeatures
will be reset to the free plan unless they have subscribed within this time.
Updating subscriptions
Stripe subscriptions can be modified relatively freely for example if moving to a custom pricing plan.
- Look up the customer on [Stripe dashboard][stripe_dashboard] using their email address or Stripe ID (this can be found in the Billing Service admin under
Customers
). - Click on the customer's current subscription.
- Click on Update subscription.
- Remove the old item from the pricing table and add the new item.
- Enterprise: Use existing enterprise prices or create new ones.
- Startup plan: Use existing Startup plan prices.
- Click on Update subscription. Do not schedule the update for a later time. There will be unintended side effects if the changes are not applied immediately.
- Do not prorate the subscription.
- The changes should be reflected for the user within a few minutes.
NOTE: Removing a metered product price (events, recordings) and adding a new price will likely reset the usage. This is fine as the Billing Service will update it during the next sync.
Self-hosted differences
Self-hosted billing is no longer supported except for legacy customers who were using the paid kubernetes deployment.