PostHog Handbook Library / Growth

3,070 words. Estimated reading time: 14 min.

Billing

Auto TL;DR

At a Glance

This long page covers these main areas. The list is generated from the article headings, so it updates with every handbook rebuild.

  1. Managing billing
  2. Credit based Plan Automation
  3. Loading contract details
  4. Upfront Payment Setup
  5. Step 1: Update Zapier table with existing Stripe ID
  6. Step 2: Create invoice
  7. Step 3: Verify invoice details and send
  8. Step 4: Apply credits

Managing billing

This section explains how PostHog's billing system works. Most billing operations described below are handled exclusively by the and are not self serve. Sales should coordinate with the billing team for any billing modifications, pricing changes, or technical billing tasks rather than attempting to implement these directly.

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.

Credit-based Plan Automation

To ensure consistency in the setup of credit-based plans we have Zapier Automation to take care of all of the Stripe-related object setup.

Loading contract details

Once an Order Form is closed in PandaDoc, Zapier will add a new row to the Credit-based Plan Table with the PandaDoc ID of the document. The table will have the following information automatically filled in: PandaDoc Order Form, Company Name, Customer Email, Credit Amount, Discount, Price, Start Date, Term, PostHog Org ID.

Upfront Payment Setup
Step 1: Update Zapier table with existing Stripe ID

If this is a new contract for an existing customer, you will need to add their existing Stripe Customer ID manually to the table. You can find this information in Vitally under Traits. If this is a brand new customer, click “Create Stripe Customer” button to assign them a new ID.

Step 2: Create invoice
Step 3: Verify invoice details and send

Do not proceed to the next steps until invoice is finalized. Any credits added to an account gets automatically applied to outstanding invoices. If you add credits before payment is completed, the credits will settle any existing debts, and customer will not be able to make a payment.

For customers using Bill.com for payment, when they submit the invoice to the Bill platform it strips out the Stripe virtual account details. You'll need to ask them to follow the instructions in this help article to set the correct bank details for us in the Bill.com platform. The account details is provided in the invoice sent over. They'll need to make sure they use the original contact information and not your email if you're set as signer on the contract so we can process payments in the right account. In case they don't do this, we have a default customer account on Stripe which the money will go to. If this happens, mark their invoice as paid manually and then generate a new one against our default customer account to use the funds.

Step 4: Apply credits
Step 5: Schedule subscription

Failed/late payments

We define late payments as follows:

  1. For credit-based customers, that have not made payment on an invoice and their due date has passed. The first invoice is usually 30 days from the contract start date (Net 30) although can differ based on other contractual terms. This rule applies to all payment terms, including and not limited to annual and quarterly, regardless whether there are still credits available or not.
  2. For pay-as-you-go usage-based customers, we will attempt 4 automated payments using the card we have on file. Each failed payment sends an alert to the #sales-alerts Slack channel. After 4 failed payments we will stop attempting to take further payments.

In either of the above scenarios the account owner as defined in Vitally needs to take action to ensure that payment is made. If there is no owner in Vitally, Simon will handle this process. If you are an AE, remember this also has impact on your commission, as we don't pay out until the customer has paid their invoice.

You can find a list of failed and overdue payments in PostHog

Step 1 - On the day their payment becomes late

As the account owner you will be assigned a risk indicator in Vitally, as well as being tagged in an alert in #sales-alerts. For unmanaged accounts with a failed payment of $1500 or more Simon and Dana are tagged instead.

You should reach out to any known contacts, as well as any finance email addresses we have in Stripe asking for payment to be made immediately. For credit-based customers, you can download the Invoice PDF from the Stripe invoice page, and for monthly customers you can get the payment link from the Stripe invoice page. To get a payment update link, click on the subscription, then click actions in the top right corner and choose share payment update link. Make it easy for them to make payment by including these details in your email.

Make it clear in this outreach that if we don't receive payment in the next 7 calendar days, their user access will be suspended. If they come back to you with genuine reasons why they need more time, use your discretion with the next steps.

Step 2 - 1 day before suspending user access

Reach out to all active users on the account, and let them know that access will be suspended tomorrow due to the failed payment. This often creates urgency and will get any late payment resolved.

Step 3 - Suspending user access

To prevent users from being able to log in you need to go to the Django admin panel for their organization, then set the "Active" field to "No", with the reason selected from the dropdown: "Access revoked due to an unpaid balance." Then, hit save.

After completing this, email or Slack all users in the organization letting them know that access has been suspended and what they can do to rectify the situation. Also make it clear that if this isn't resolved within the next 7 days we will revert them back to the Free tier and they be subjected to the usage limits of that tier (e.g. they are likely to lose tracking data).

If they do pay after this point make sure to re-enable user access by reversing the above in Django admin.

Step 4 - 1 day before cancelling their subscription

Reach out to all contacts letting them know that due to the failed payment we will be terminating their subscription tomorrow.

Make it clear in this outreach that once the subscription is terminated they will be subject to the free tier usage limits and we won't store any data above that limit.

Step 5 - Cancelling their subscription

You can cancel their subscription in Stripe - navigate to their Stripe customer page, and then click the ... next to their active subscription to find the Cancel option.

At this point they will be notified about this automatically via the billing service.

Repeated failed payments

After three consecutive missed payment periods, the customer must provide advance payment covering three months of service based on their typical usage before account access is restored. If the customer disagrees or fails to make the advance payment, the account may be reverted to the Free Tier.

India-based customers

Withholding taxes

PostHog Inc is a US incorporated company and a US tax resident and we do not claim benefits under any Double Taxation Avoidance Agreements (DTAA). To support this, we provide:

These documents are available in the shared Finance drive. You can share them with the customer on request.

The full invoice amount is due. Any tax withheld is exclusive of the invoice, which will be treated as outstanding.

Stripe Products & Prices

⚠️ Product and price modifications are restricted and handled exclusively by the . These changes are only made in rare cases and require billing team approval and implementation. Do not attempt to modify products or prices directly - contact the billing team for any pricing-related requests.

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:

Image: Stripe products

Custom metadata

On Stripe Products

On Stripe Product Prices The following keys are used to manage Startup prices:

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:

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 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.

  1. Visit the customer in the Stripe dashboard.
  2. Select Actions -> Apply Coupon.
  3. Select the coupon to apply.
  4. 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_.

Creating new or bespoke prices
  1. Go to the appropriate product in question (do not create your own Product)
  2. Click "Add another price"
  3. Important: For metered products (e.g. Product Analytics, Session Replay), set up the price as follows:
  1. Expand the additional options and add a straightforward Price Description like Custom - {date of creation}
  2. Add the tiers as you see fit
  1. Add custom metadata if needed.

Plans

⚠️ Plan modifications are handled exclusively by the . Do not attempt to modify plans directly, contact the billing team for any plan related requests.

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.

Employees can get access to paid features (like Boost) on personal or side projects. Ask in #team-billing with your organization ID and someone can set this up. There are two approaches for platform add-ons:

  1. Special billing-only plan: Add a plan like boost-addon-20250602 to the customer's plans_map in the billing admin. These plans exist only in the billing system and grant features without a Stripe subscription.
  1. Long trial: Create a trial that does not auto-convert with a long expires_at date. This works well for temporary access or when you want a clear end date.

Updating subscriptions

Stripe subscriptions can be modified relatively freely for example if moving to a custom pricing plan.

Image: Stripe subscription update

  1. 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).
  2. Click on the customer's current subscription.
  3. Click on _Update subscription_.
  4. Remove the old item from the pricing table and add the new item.
  1. 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.
  2. Do not prorate the subscription.
  3. 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.

Billing for data pipelines

For information about data pipeline pricing and billing, please visit our pricing page.

Canonical URL: https://posthog.com/handbook/growth/sales/billing

GitHub source: contents/handbook/growth/sales/billing.md

Content hash: afed4ab989e91c59

Static reader notes
  • MDX_COMPONENT_STATIC_ADAPTER: Adapted interactive MDX components for static reading: PrivateLink, SmallTeam.