In August 2025, we helped our friends at Steady to migrate their 600,000+ users to their own in-house billing system. This was the final step of an internal project called “Charger”. Its aim was to replace the off-the-shelf payment platform they had started with, Chargebee, which had become a roadblock. The work on Charger began already in 2021 in workshops with the Steady team. Together with them, we designed, planned and finally built the internal product that would take over for years later. The process and the implementation happened in multiple iterations over a span of four years.
This blog post explores the strategy that was used to achieve this challenging task.
The backstory
Steady supports Media Makers in building an audience and driving revenue via subscriptions to newsletters, blogs etc. While most of the tools and features for Media Makers live in their main application, they outsourced the subscriptions & billing management to a third-party service (Chargebee). The Media Makers app was very tightly coupled to Chargebee as it relied on it for core business logic within the app, as well as for accounting.

Introducing: Charger, the fraternal twin
Although Chargebee was useful initially, it became increasingly painful to work around their standard processes over time. This ultimately led to the development of a custom billing tool.
For this we had to reverse-engineer all of what Chargebee was doing. We started by building the required schemas, and exposing the API endpoints and webhook notifications that were needed by the Media Makers app. Any changes in the Media Makers app usage of the API/Webhooks, or to Chargebee itself, needed to be propagated to Charger. Both billing systems had to behave identical from the outside.
The Media Makers app now needed to forward calls to Charger and/or Chargebee. A “multiplexer“ abstraction close to the API & Webhook layer was added, so that for developers & users of the app, the fact that Charger or Chargebee was used for the calls would be transparent.

Charger needed to process payments and refunds, with all payment providers supported by the Media Makers app. The first flow of the application was created: subscribe to a publication, generate an invoice, create a payment, and settle that payment 🎉!
🔍 Would you like more detail? We have a 'behind the scenes' blog post about Steady! Have a Look!
A bird watching experience 🔭
At this stage, no real users were migrated to Charger. No real users were even created on Charger! The system just worked™... Subscription statuses, invoices, payments processing being crucial to the system meant that we had to come up with a transition plan, that allowed the developers to observe and watch the behaviour of the system for just a handful of users. If any problem arose, it would need to affect only such a small amount of users that their support team could resolve it manually. We handpicked 10 users with rather simple data: a single recent subscription, no cancellations, and various payment providers / currencies to cover as much ground as possible.

Using our beloved Oban for job processing, we built a migration job, that would, for a given user:
- Fetch all their data from Chargebee
- Format it to please Chargerʼs data model
- Send it to a dedicated endpoint in Charger
- Charger creates all the given data for the user (subscriptions, invoices, payments, etc.)
- Cancel the user subscriptions on Chargebee (so that they donʼt get billed twice!)
Of course, all of this must happen in a transaction on both systems, fail gracefully and report meaningful errors to the devs. We leveraged Oban configuration to optimise the parallelisation and retry mechanism of the jobs, as we could be rate-limited by Chargebee. At some point, we would want to migrate users in batches of 100-10k users at a time, so the migration mechanism had to be robust and auto-healing.
💡 Have we got you interested? If you have a question or topic you would like to discuss with us, we would like to hear from you. Book a free call with us
Silent bugs
A main challenge in this process was to validate the integrity of the migrated data. The Media Makers app and Charger run on different databases, and have their own representation of what is an invoice, a subscription, a user, etc. Forgetting to migrate a field, or mapping the wrong timestamp from one system to the other, would happen silently because a part of the migration code lives in the Media Makers app, and the other part lives in Charger. The data could be silently missing or corrupted, and we would only realise down the road that all users were migrated with errors. Luckily, we had the chance of having access to a Chargebee staging instance with users in all kinds of configuration. This helped us a lot in finding migration issues by comparing the user data on both systems before and after the migration, and strengthening our tests to cover all the edge cases we could find. The developers at Steady also added ERPC, which enabled us to run integration tests evaluating the effects of Charger on the Media Makers app.
Conclusion
Once we were confident that our 10 users were rolling, we migrated 10 more users with more complex data. And so on, for weeks, increasing the batch size, and deciding to create new users on Charger with a rand() choice. This allowed Steady to steer the future of their billing strategy for their users, integrating with other payment providers and simplifying some of the user flows for refunds etc. While it looks simple on paper, this project required a lot of patience, collaboration and planning ahead as the stakes were really high and we had to build up confidence in the new system and in the migration approach.
