Zulip uses a third party (Stripe) for billing, so working on the billing system requires a little bit of setup.

To set up the development environment to work on the billing code:

  • Create a Stripe account
  • Go to, and add the publishable key and secret key as stripe_publishable_key and stripe_secret_key to zproject/dev-secrets.conf.
  • Run ./ setup_stripe.

It is safe to run setup_stripe multiple times.

Nearly all the billing-relevant code lives in zilencer/.

General architecture


  • Anything that talks directly to Stripe should go in zilencer/lib/
  • We generally try to store billing-related data in Stripe, rather than in Zulip database tables. We’d rather pay the penalty of making extra stripe API requests than deal with keeping two sources of data in sync.

The two main billing-related states for a realm are “have never had a billing relationship with Zulip” and its opposite. This is determined by Customer.objects.filter(realm=realm).exists(). If a realm doesn’t have a billing relationship, all the messaging, screens, etc. are geared towards making it easy to upgrade. If a realm does have a billing relationship, all the screens are geared toward making it easy to access current and historical billing information.

Note that having a billing relationship doesn’t necessarily mean they are on a paid plan, or have been in the past. E.g. adding a coupon for a potential customer requires creating a Customer object.


  • When manually testing, I find I often run Customer.objects.all().delete() to reset the state.
  • 4242424242424242 is Stripe’s test credit card, also useful for manually testing. You can put anything in the address fields, any future expiry date, and anything for the CVV code.