Tutorial to Set Up Usage-Based API Billing with Moesif and Chargebee

Modern API businesses are migrating towards usage-based billing models which enables automatic expansion revenue while removing barriers to adopting a new API. With Moesif you can integrate with subscription management solutions like Recurly and Chargebee to quickly add advanced usage-based billing in a few minutes. In this guide, we’ll walk through integrating Moesif with Chargbee and some recommendations for usage-based billing. Then, we’ll discuss how to leverage embed templates and behavioral emails to keep your customers informed of their usage.

This guide assumes you’re already familiar with Moesif’s Metered API Billing and Chargebee’s Subscription Management. We also recommend reading A Playbook to Properly Implement Pay As You Go Pricing to become familiar with different billing models.

How the solution works

In order to properly implement usage-based billing, you need a few components beyond standard subscription management:

  • Meter each customer’s platform usage and invoice based on that usage
  • Send automatic reminders to customers when they approach or exceed a quota or threshold
  • Provide a dashboard for a customer to inspect their subscription usage

This example walks through setting up postpaid billing where customers are monthly based on their last month’s usage. While this is the most common form of usage-based billing, there are alternative models such as prepaid billing, credit-based balances, and threshold-based invoicing. Review A Playbook to Properly Implement Pay As You Go Pricing for an in-depth comparison.

A working example app is available on GitHub

On each renewal period (usually monthly), Chargebee will call a webhook to get the customer’s usage from Moesif. This is done via a predefined query within your Moesif account. Moesif will also send reminder emails when a customer approaches a predefined quota which could be customer specified.

1. Install the Moesif monitoring SDK

In order to handle usage-based billing, you will need to meter each customer’s API usage accurately. This can be done with a Moesif server integration or API gateway plugin. An example is with Node.js below.

// 1. Import Modules
const express = require('express');
const app = express();
const moesif = require('moesif-nodejs');

// 2. Set the options, the only required field is applicationId
const moesifMiddleware = moesif({
  applicationId: 'Your Moesif Application Id',

  // Optional hook to link API calls to users
  identifyUser: function (req, res) {
    return req.user ? req.user.id : undefined;
  },
});

// 3. Enable the Moesif middleware to start logging incoming API Calls
app.use(moesifMiddleware);

2. Sync Chargebee Subscription info to Moesif

In order to properly send quota and billing notifications to customers, you’ll need to store the customer email in Moesif. You’ll also want to store the customer’s plan. We recommend the following mapping:

Moesif Chargebee Description
User Customer / Contact A single user at an account with their name, email, and user id
Company Subscription Subscription info with it’s associated plan quota or price

Handling multiple emails per account

In Chargebee, each Subscription object can only be associated with a single Customer object. This somewhat complicates the data model if you can have many seats (users) for a single Subscription. As a workaround, Chargebee does enable you to store two special Contacts: the Account Email and the Billing Email.

Because Moesif is designed for account-based businesses (i.e. B2B), Moesif handles users differently. A single Moesif Company can be associated with many Users, each with their own metadata. We recommend syncing all Contacts under the Customer to Moesif by splitting the Customer object, not just the primary Customer which enables more flexibility into who receives email notifications. Each user has a field that specifies the label or role of the user.

To sync the Chargebee subscriptions to Moesif, we’ll fetch all the recently created or updated subscriptions from the last 24 hours using a recurring cron job. The Chargebee Subscription Id will map to the Moesif Company id where as the Customer Id/Contact Id will map to a Moesif User Id.

Changes in Chargebee

  • In the Billing LogIQ settings, select Notify and wait to close Invoices.
  • For the invoice date, we recommend Date of closing invoice

Code to sync via cron job

Below we are saving all subscription fields for analytics, but you can choose which properties to save as the Moesif company or user metadata. We are looping through both subscriptions and companies. The Chargebee API limits pagination to 100 items, so we keep repeating the code after a silence period.

A working example app is available on GitHub

const chargebee = require('chargebee');
const cron = require('cron');
const basicAuth = require('express-basic-auth');
const bodyParser = require('body-parser');
const express = require('express');
const safeGet = require('lodash/get');
const superagent = require('superagent');
const moesifapi = require('moesifapi');
const moment = require('moment');

// Initialize SDKs
chargebee.configure({ site: process.env.CHARGEBEE_SITE, api_key: process.env.CHARGEBEE_API_KEY });

moesifapi.configuration.ApplicationId = process.env.MOESIF_APPLICATION_ID;

chargebee.configure({ site: process.env.CHARGEBEE_SITE, api_key: process.env.CHARGEBEE_API_KEY });

const CronJob = cron.CronJob;
new CronJob('*/10 * * * *', function() {
  try {
    console.log('Run syncSubscriptions');
    syncSubscriptions();
  } catch (err) {
    console.log(err);
  }
}, null, true, 'America/Los_Angeles', null, true);

function syncSubscriptions() {
    chargebee.subscription
    // We only need to sync subscriptions that have been updated in the last 24 hrs. 
    .list({
        limit: 100,
        'sort_by[asc]': 'updated_at',
        'updated_at[after]': moment().utc().subtract(24, 'hour').unix(),
    })
    .request()
    .then((subscriptions) => {

            console.log(`Received subscriptions`)

            // Save Chargebee subscriptions as Moesif companies
            const companies = subscriptions.list.map((s) => {
                return {
                    company_id: s.subscription.id,
                    metadata: s.subscription // Rest of metadata
                }
            })

           // console.log(JSON.stringify(companies));
            moesifapi.ApiController.updateCompaniesBatch(companies, (error, response, context) => {
                if (error) {
                    console.log(error) 
                } else {
                    console.log(`Synced Chargebee Subscriptions to Moesif Companies Successfully statusCode=${context.response.statusCode}`)
                }

                // Save Chargebee customers and contacts as Moesif users
                const users = subscriptions.list.map((s) => {
                    const contacts = s.customer.contacts ? 
                        contacts.map(c => {
                            return {
                                user_id: c.id,
                                company_id: s.subscription.id,
                                email: c.email,
                                first_name: c.first_name,
                                last_name: c.last_name,
                                metadata: { label: c.label, ...s.customer } // Rest of metadata
                            };
                        }) : [];

                    return [
                        ...contacts, 
                        {
                            user_id: s.customer.id,
                            company_id: s.subscription.id,
                            email: s.customer.email,
                            first_name: s.customer.first_name,
                            last_name: s.customer.last_name,
                            metadata: s.customer // Rest of metadata
                        }
                    ];

                });

                usersFlattened = users.reduce(function(a, b){
                    return a.concat(b);
               }, []);

                moesifapi.ApiController.updateUsersBatch(usersFlattened, (error, response, context) => { 
                    if (error) {
                        console.log(error) 
                    } else {
                        console.log(`Synced Chargebee Subscriptions to Moesif Users Successfully statusCode=${context.response.statusCode}`)
                    }
                });
            });
        }
    );
}

3. Handle metered billing webhooks

Chargebee’s Invoice API has a convenient webhook that can be triggered on invoice generation. This gives you a way to tell Chargbee how much to charge the customer based on their usage in Moesif for the customer’s billing period.

You can add the charge in one of two ways:

For the purpose of this example, we’ll directly add a charge to the invoice. However, more complex set ups may want to leverage add-ons.

Calculating billing period

Once an invoice is in a pending state, the subscription has already been renewed such that we started a brand new billing period. Because of this, we want to calculate a customer’s usage based on the last billing period, not the current.

Company usage query

In order to determine a customer’s usage, we first need to decide what API calls are “billable” which is determined by your pricing and business model. For example, a communications API may charge based on SMS sent whereas an e-commerce platform may charge based on purchases made. We can leverage Moesif’s advanced filtering mechanism and Management API to build this query in a few clicks. In this example, we are including all API calls where the route matches /purchases or /withdraws.

  • To generate the JSON in the getCompanyUsageQuery() function, log into Moesif and go to Events -> Segmentation.
  • From here, add any filters that should be included such as specific endpoints or fields.

Create Management API query

  • Be sure to add a filter on a company id. The actual value and the chart’s date range don’t matter as they will be overridden in the code.
  • Click the orange “Embed” button at the top right and then “Get Via API”. This will provide the full JSON query to use for that report.
  • The query params from and to along with the payload value company_id.raw will be will be overridden in the code below.

Get Via API

The code to handle usage-based invoicing

A working example app is available on GitHub

const chargebee = require('chargebee');
const cron = require('cron');
const basicAuth = require('express-basic-auth');
const bodyParser = require('body-parser');
const express = require('express');
const safeGet = require('lodash/get');
const superagent = require('superagent');
const moesifapi = require('moesifapi');
const moment = require('moment');

const app = express();
chargebee.configure({ site: process.env.CHARGEBEE_SITE, api_key: process.env.CHARGEBEE_API_KEY });

app.use(
  basicAuth({
    users: { [process.env.CHARGEBEE_WEBHOOK_USERNAME]: process.env.CHARGEBEE_WEBHOOK_PASSWORD },
  })
);
const UNIT_COST_IN_CENTS = 1; // How much each transaction is worth in cents

// Simple sample query which can be found going to "Events" -> "Segmentation" in Moesif.
// Then select the orange Get Via API button under "Embed".
function getCompanyUsageQuery(companyId) {
    return {
        aggs: {
            seg: {
                filter: {
                    bool: {
                        must: [
                            {
                                terms: {
                                    'request.route.raw': [
                                        '/purchases',
                                        '/withdraws',
                                        '/'
                                    ]
                                }
                            },
                            {
                                term: {
                                    'company_id.raw': companyId
                                }
                            }
                        ]
                    }
                },
                aggs: {
                    weight: {
                        sum: {
                            field: 'weight',
                            missing: 1
                        }
                    }
                }
            }
        },
        size: 0
    }
}

app.use(bodyParser.json());
app.post('/chargebee/webhooks', (req, res) => {
    const event = req.body;
  
    if (event && event.event_type === 'pending_invoice_created') {

        const invoice = event.content.invoice;

        // Retrieve subscription for this invoice to get billing period
        return chargebee.subscription
            .retrieve(invoice.subscription_id)
            .request()
            .then((subscriptionResult) => {
                const subscription = subscriptionResult.subscription;
                console.log('Retrieved subscription');
                console.log(JSON.stringify(subscription));

                // We should query metric the previous billing period.
                const params = {
                    from: (moment.unix(subscription.current_term_start)
                        .subtract(parseInt(subscription.billing_period), `${subscription.billing_period_unit}s`)).toISOString(),

                    to: moment.unix(subscription.current_term_start).toISOString()
                };

                console.log('Params: ' + moment.unix(subscription.current_term_start).toISOString());
                console.log(JSON.stringify(params));

                // Get usage from Moesif
                return superagent
                    .post(`https://api.moesif.com/v1/search/~/search/events`)
                    .set('Authorization', `Bearer ${process.env.MOESIF_MANAGEMENT_API_KEY}`)
                    .set('Content-Type', 'application/json')
                    .set('accept', 'json')
                    .query(params)
                    .send(getCompanyUsageQuery(subscription.id))
                    .then((countResult) => {

                        const count = safeGet(countResult, 'body.aggregations.seg.weight.value');

                        console.log(`Received count of ${count}`)
                        const amount = count * (UNIT_COST_IN_CENTS); // How much each transaction is worth in cents

                        console.log(`Adding cost for subscription=${subscription.id} of ${amount}`);

                        chargebee.invoice.add_charge(invoice.id,{
                            amount : amount,
                            description : `Usage of ${amount} widgets`
                        }).request(function(error, chargeResult) {
                            if(error){
                                //handle error
                                console.log(JSON.stringify(event));
                                console.log(error);
                                res.status(500).json({ status: 'internal server error' });
                            } else {
                                console.log(chargeResult);
                                res.status(201).json({ status: 'ok ' });
                            }
                        });
                    })
                    .catch((error) => {
                        console.log(error.text);
                        res.status(500).json({ status: 'internal server error' });
                    });
            })
    } 
    console.log(JSON.stringify(event));
    res.status(500).json({ status: 'internal server error' });
});

app.listen(process.env.PORT || 5000, function() {
    console.log('moesif-chargebee-example is listening on port ' + (process.env.PORT || 5000));
});

3. Embed usage reports

Now that your usage-based invoicing is fully set up up with Moesif and Chargebee, you’ll also want to look into ways to keep your customers informed of their usage. The two ways this can be done. The first being embedded dashboards so you can show metrics on usage.

Embedded billing dashboard

Embed usage reports enable you to provide self-serve metrics to your customers so they understand what they are paying for. These reports create additional transparency and trust with your customers since they can track the value they get relative to cost. To get started, view embedded template docs and the example GitHub project

4. Set up quota alerts and emails

You can also leverage Moesif Behavioral Emails to automatically inform customers when they are close to or exceeding their quota for the billing period. This can be a static threshold, specific to a plan, or even specific to groups of customers.

Subscription quota emails

To get started, follow our guide to set up automatic notifications of quota and billing issues.

Closing thoughts

With Moesif and Chargebee, you now have an accurate mechanism to bill customers based on their usage. Because the telemetry is already stored in Moesif, you can leverage its additional widgets like behavioral emails and embedded dashboards to keep your customers informed of their subscription with just a few clicks. In addition, you can also set up alerts in Moesif so your own customer success team can proactively reach out, which is critical when a bill can fluctuate drastically between billing periods. To get started, view the example app available on GitHub.

Automatically handle API metering and quota emails with Moesif

Automatically handle API metering and quota emails with Moesif

Learn More