This guide will walk you through how to send usage into Maxio to bill your customers based on usage. When you're finished, you will have an API integration that automatically sends usage into Maxio.
Summary
- Feature
- Usage Components and Maxio Metering
- You Need
- Developer experienced with APIs; a web app with Server-side code
- Code
- Yes
- Difficulty
- ◉◉◉◎◎
- Streams vs. Batches
-
-
Metering
Choose Streams if you use Maxio for metering; Choose Batches if you plan to pre-aggregate your data before sending to Maxio. -
Timing
Streams are real-time; Batches are scheduled/periodic. -
Attributes
Streams support rating across up to four attributes; Batches only support single attribute rating. -
API Calls
Streams use the /events endpoint; Batches use the /usages endpoint.
For details on when to use Streams or when to use Batches, read the Streams vs. Batches article.
-
Metering
Send Usage with Streams
Sending usage to Maxio is one of the most important elements of your usage-based billing implementation. Streaming is useful for any combination of high-volume events, data aggregation, multi-attribute pricing, and a desire to iterate on pricing in the future.
To begin Metering with Streams, you'll need to have the feature enabled for your site. By completing these steps, your site will be prepared to handle event Streams and Meters.
-
Contact Your Customer Success Manager
Reach out to your designated Customer Success Manager or Maxio support representative to request the activation of the Metering feature for your account. -
Configure Metering Settings
Once Metering is enabled, navigate to the Metering settings within the Maxio platform:- Go to Config > Settings > Metering and turn on the Use Metering setting.
- (Optional) Adjust the Subscription Processing Grace Period which provides additional time to send in usage data before a subscription is processed. Enter the desired number of minutes and click Save Settings.
By completing these steps, your site will be prepared to handle event streams and meters, laying the foundation for effective usage-based billing.
In the context of event data modeling, an event is a discrete action or occurrence within your application that holds significance, such as a user making a purchase, updating their profile, or adding an item to a shopping cart. These events capture specific moments in time and provide valuable insights into user behavior and system performance. When these events occur in a related sequence, they form an event stream. For example, a user's journey through an e-commerce site—from logging in, browsing products, adding items to the cart, and completing a purchase—constitutes an event stream.
Each event encompasses various properties that offer detailed insights into the event's context. Events in an Stream should have similar properties. For example, all logins share properties like first name, last name, app version, platform, and time since last login. These properties can describe the event itself, the actor performing it, session specifics, and environmental factors.
Accurate timestamps are also essential for accurate billing. By default if a timestamp is not passed, we will use the system timestamp when the event is received. You may also use the chargify.timestamp
to specify the exact time. All timestamps are in UTC.
For a comprehensive understanding and detailed guidance on event data modeling, refer to the full Event Data Modeling article.
A Stream is a collection of related events that Maxio uses to track and process usage data. Each stream has a unique API endpoint where you send event data.
- In the Maxio UI, navigate to Metering > Streams.
- Click New Event Stream.
- Enter a Name that reflects the type of events being tracked (e.g., "API Requests" or "Storage Usage").
- Set an API handle.
- (Optional) Add a Description to clarify its purpose.
- Pick a Subscription Identifier (subscription_id, subscription_reference, other).
- Click Create Stream.
By creating a stream and defining its API endpoint, you establish the foundation for tracking and analyzing usage data in Maxio. In the next step, you'll begin sending events to this stream.
Once you’ve created a stream, you can start sending event data to Maxio in real time. Each stream has a unique API endpoint, which you’ll use to report usage events.
-
Locate the Stream’s API Endpoint:
- In the Maxio UI, navigate to Metering > Streams.
- Select the Stream you created.
- Copy the API endpoint URL displayed in the stream details.
-
Send Events via API:
-
Use the POST method to send JSON-formatted event data to the stream’s API endpoint.
-
Each event should include relevant data, such as a timestamp, subscription identifier, and event-specific attributes.
-
This is an API call to create events-based usage, using the /events endpoint for single events and /events/bulk for bulk events up to 1,000 at a time. Set your events API endpoint, and your JSON.
# Event Ingestion - https://developers.chargify.com/docs/api-docs/b8ffb590f236a-event-ingestion require 'uri' require 'net/http' require 'openssl' url = URI("https://events.chargify.com/subdomain/events/api_handle.json") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Post.new(url) request["Content-Type"] = 'application/json' request["Authorization"] = 'Basic BASE64APIKEY' request.body = "{\n \"chargify\": {\n \"subscription_id\": 1,\n \"timestamp\": \"2020-02-27T17:45:50-05:00\"\n },\n \"messages\": 150,\n \"country\": \"US\"\n}" response = http.request(request) puts response.read_body
# Event Ingestion - https://developers.chargify.com/docs/api-docs/b8ffb590f236a-event-ingestion import http.client
import json
import base64 conn = http.client.HTTPSConnection("events.chargify.com")
payload = json.dumps({
"chargify": {
"subscription_id": 1,
"timestamp": "2025-02-17T23:46:39-05:00
},
"messages": 150,
"country": "US"
})
# Encode your API key
api_key = "Basic BASE64APIKEY"
encoded_key = base64.b64encode(f"{api_key}:".encode()).decode()
headers = {
'Content-Type': "application/json",
'Authorization': f"Basic {encoded_key}"
} conn.request("POST", "/subdomain/events/api_handle.json", payload, headers) res = conn.getresponse() data = res.read() print(data.decode("utf-8"))// Event Ingestion - https://developers.chargify.com/docs/api-docs/b8ffb590f236a-event-ingestion var axios = require("axios").default; var options = { method: 'POST', url: 'https://events.chargify.com/subdomain/events/api_handle.json', headers: { 'Content-Type': 'application/json', Authorization: 'Basic BASE64APIKEY' }, data: { chargify: {subscription_id: 1, timestamp: '2020-02-27T17:45:50-05:00'}, messages: 150, country: 'US' } }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); });
// Event Ingestion - https://developers.chargify.com/docs/api-docs/b8ffb590f236a-event-ingestion $request = new HttpRequest(); $request->setUrl('https://events.chargify.com/subdomain/events/api_handle.json'); $request->setMethod(HTTP_METH_POST); $request->setHeaders([ 'Content-Type' => 'application/json', 'Authorization' => 'Basic BASE64APIKEY' ]); $request->setBody('{ "chargify": { "subscription_id": 1, "timestamp": "2020-02-27T17:45:50-05:00" }, "messages": 150, "country": "US" }'); try { $response = $request->send(); echo $response->getBody(); } catch (HttpException $ex) { echo $ex; }
// Event Ingestion - https://developers.chargify.com/docs/api-docs/b8ffb590f236a-event-ingestion AsyncHttpClient client = new DefaultAsyncHttpClient(); client.prepare("POST", "https://events.chargify.com/subdomain/events/api_handle.json") .setHeader("Content-Type", "application/json") .setHeader("Authorization", "Basic BASE64APIKEY") .setBody("{\n \"chargify\": {\n \"subscription_id\": 1,\n \"timestamp\": \"2020-02-27T17:45:50-05:00\"\n },\n \"messages\": 150,\n \"country\": \"US\"\n}") .execute() .toCompletableFuture() .thenAccept(System.out::println) .join(); client.close();
// Event Ingestion - https://developers.chargify.com/docs/api-docs/b8ffb590f236a-event-ingestion var client = new HttpClient(); var request = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri("https://events.chargify.com/subdomain/events/api_handle.json"), Headers = { { "Authorization", "Basic BASE64APIKEY" }, }, Content = new StringContent("{\n \"chargify\": {\n \"subscription_id\": 1,\n \"timestamp\": \"2020-02-27T17:45:50-05:00\"\n },\n \"messages\": 150,\n \"country\": \"US\"\n}") { Headers = { ContentType = new MediaTypeHeaderValue("application/json") } } }; using (var response = await client.SendAsync(request)) { response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); Console.WriteLine(body); }
# Bulk Event Ingestion - https://developers.chargify.com/docs/api-docs/f8128bcd44d55-bulk-event-ingestion require 'uri' require 'net/http' require 'openssl' url = URI("https://events.chargify.com/subdomain/events/api_handle/bulk.json") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Post.new(url) request["Content-Type"] = 'application/json' request["Authorization"] = 'Basic BASE64APIKEY' request.body = "[\n {}\n]" response = http.request(request) puts response.read_body
# Bulk Event Ingestion - https://developers.chargify.com/docs/api-docs/f8128bcd44d55-bulk-event-ingestion import http.client conn = http.client.HTTPSConnection("events.chargify.com") payload = "[\n {}\n]" headers = { 'Content-Type': "application/json", 'Authorization': "Basic BASE64APIKEY" } conn.request("POST", "/subdomain/events/api_handle/bulk.json", payload, headers) res = conn.getresponse() data = res.read() print(data.decode("utf-8"))
// Bulk Event Ingestion - https://developers.chargify.com/docs/api-docs/f8128bcd44d55-bulk-event-ingestion var axios = require("axios").default; var options = { method: 'POST', url: 'https://events.chargify.com/subdomain/events/api_handle/bulk.json', headers: { 'Content-Type': 'application/json', Authorization: 'Basic BASE64APIKEY' }, data: [{}] }; axios.request(options).then(function (response) { console.log(response.data); }).catch(function (error) { console.error(error); });
// Bulk Event Ingestion - https://developers.chargify.com/docs/api-docs/f8128bcd44d55-bulk-event-ingestion $request = new HttpRequest(); $request->setUrl('https://events.chargify.com/subdomain/events/api_handle/bulk.json'); $request->setMethod(HTTP_METH_POST); $request->setHeaders([ 'Content-Type' => 'application/json', 'Authorization' => 'Basic BASE64APIKEY' ]); $request->setBody('[ {} ]'); try { $response = $request->send(); echo $response->getBody(); } catch (HttpException $ex) { echo $ex; }
// Bulk Event Ingestion - https://developers.chargify.com/docs/api-docs/f8128bcd44d55-bulk-event-ingestion AsyncHttpClient client = new DefaultAsyncHttpClient(); client.prepare("POST", "https://events.chargify.com/subdomain/events/api_handle/bulk.json") .setHeader("Content-Type", "application/json") .setHeader("Authorization", "Basic BASE64APIKEY") .setBody("[\n {}\n]") .execute() .toCompletableFuture() .thenAccept(System.out::println) .join(); client.close();
// Bulk Event Ingestion - https://developers.chargify.com/docs/api-docs/f8128bcd44d55-bulk-event-ingestion var client = new HttpClient(); var request = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri("https://events.chargify.com/subdomain/events/api_handle/bulk.json"), Headers = { { "Authorization", "Basic BASE64APIKEY" }, }, Content = new StringContent("[\n {}\n]") { Headers = { ContentType = new MediaTypeHeaderValue("application/json") } } }; using (var response = await client.SendAsync(request)) { response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); Console.WriteLine(body); }
-
-
Verify Successful Event Submission:
- A successful request returns a 2xx response.
- If errors occur, check the response message for details and adjust your payload accordingly.
A Meter processes and analyzes events from a stream to generate usage data. Meters allow you to measure usage based and apply pricing rules based on event attributes.
-
Create a New Meter:
- In the Maxio UI, navigate to Metering > Meters.
- Click Create New Meter.
-
Select a Stream
Choose the Stream you created in Step 3. The Meter will process events from this stream to compute usage. -
Fill in Meter Details.
- Provide a Name that describes what you’re measuring (for example, "API Calls per Hour").
- (Optional) Add a Description to provide context.
-
Define Base Analysis Rules:
- Select a Analysis Type (for example, Count, Sum, Average).
- Select the Target Property, if applicable.
- Set the Segmenting Attributes:These are the event properties by which you will define your pricing segments.
-
Save the Meter:
- Review your configuration.
- Click Create Meter.
The Meter will now track and calculate usage based on incoming event data.
By setting up a Meter, you enable Maxio to quantify usage, which can later be linked to a Metered Component for billing.
A Metered Component allows you to bill customers based on usage measured by a Meter. This step links the Meter to a Component that can be added to Subscriptions.
-
Create a New Metered Component:
- In the Maxio UI, navigate to Products > Components.
- Click New Component and select Metered Component as the type.
- Enter a Name. This name will appear on the invoice.
-
Attach the Meter
Under the Component Details section, select the Meter created in Step 5.
This links the component to the Meter, allowing it to track and bill based on event data. -
Define Pricing:
- Choose a Pricing Scheme (for example, Per Unit, Tiered, Volume). Set the appropriate pricing structure based on your billing model.
- For multi-attribute Meters, this first price will become the price for the catch-all usage segment.
- For multi-attribute Meters, once the price for the catch-all segment is defined, you can define additional segment prices.
-
Save the Component:
- Review your settings.
- Click Create Component.
The metered component is now ready to be activated on subscriptions.
By attaching a meter to a metered component, you ensure that usage data is accurately tracked and billed to customers.
To begin tracking and billing usage, you must add the component to a subscription. This can be done at the time of subscription creation or in two successive actions. Below are the steps for activating the component after subscription creation.
-
Locate the Subscription:
- In the Maxio UI, navigate to Billing > Subscriptions.
- Search for and select the subscription you want to update.
-
Add the Metered Component:
- From the Subscription Summary, navigate the the Subscription Components tab.
- Choose the Metered Component you created in Step 6, and from the action button, select Activate Billing.
- Confirm the Activation.
The component will now appear in the subscription details. Any usage reported through the Meter will be recorded and billed accordingly. By activating the Component on a Subscription, you enable automated tracking and billing for usage-based charges.
Server-side
Next, you'll need to put the call in the right place inside your code. The general structure is:
- Your backend script processes the event.
- It verifies the event is successful (if you only send usage on success).
- It sends the usage API request to Maxio.
Create several subscriptions, each for your real use cases with usage. Simulate action in your app that triggers your usage workflow.
- Test that the usage is immediately sent to Maxio.
- Ensure that after the event occurs in your app, the usage goes to the proper subscriptions.
- See that your usage is rated as you intended. You can create a component with a 1 day billing interval or use the Proforma Invoice.
Send Usage in Batches
Sending usage to Maxio is one of the most important elements of your usage-based billing implementation. Batching is useful if you already have a usage aggregation process in place, do not require multi-attribute pricing, and do not plan to iterate on pricing in the future.
subscription_id
and component_id
, and you pass the usage quantity
, an optional price_point_id
, and an optional memo
.
# Create Usage - https://developers.chargify.com/docs/api-docs/9954fb40d117d-create-usage
require 'uri'
require 'net/http'
require 'openssl'
url = URI("https://subdomain.chargify.com/subscriptions/subscription_id/components/component_id/usages.json")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request["Authorization"] = 'Basic BASE64APIKEY'
request.body = "{\n \"usage\": {\n \"quantity\": 1000,\n \"price_point_id\": \"149416\",\n \"memo\": \"My memo\"\n }\n}"
response = http.request(request)
puts response.read_body
# Create Usage - https://developers.chargify.com/docs/api-docs/9954fb40d117d-create-usage
import http.client
conn = http.client.HTTPSConnection("subdomain.chargify.com")
payload = "{\n \"usage\": {\n \"quantity\": 1000,\n \"price_point_id\": \"149416\",\n \"memo\": \"My memo\"\n }\n}"
headers = {
'Content-Type': "application/json",
'Authorization': "Basic BASE64APIKEY"
}
conn.request("POST", "/subscriptions/subscription_id/components/component_id/usages.json", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
// Create Usage - https://developers.chargify.com/docs/api-docs/9954fb40d117d-create-usage
var axios = require("axios").default;
var options = {
method: 'POST',
url: 'https://subdomain.chargify.com/subscriptions/subscription_id/components/component_id/usages.json',
headers: {
'Content-Type': 'application/json',
Authorization: 'Basic BASE64APIKEY'
},
data: {usage: {quantity: 1000, price_point_id: '149416', memo: 'My memo'}}
};
axios.request(options).then(function (response) {
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
// Create Usage - https://developers.chargify.com/docs/api-docs/9954fb40d117d-create-usage
$request = new HttpRequest();
$request->setUrl('https://subdomain.chargify.com/subscriptions/subscription_id/components/component_id/usages.json');
$request->setMethod(HTTP_METH_POST);
$request->setHeaders([
'Content-Type' => 'application/json',
'Authorization' => 'Basic BASE64APIKEY'
]);
$request->setBody('{
"usage": {
"quantity": 1000,
"price_point_id": "149416",
"memo": "My memo"
}
}');
try {
$response = $request->send();
echo $response->getBody();
} catch (HttpException $ex) {
echo $ex;
}
// Create Usage - https://developers.chargify.com/docs/api-docs/9954fb40d117d-create-usage
AsyncHttpClient client = new DefaultAsyncHttpClient();
client.prepare("POST", "https://subdomain.chargify.com/subscriptions/subscription_id/components/component_id/usages.json")
.setHeader("Content-Type", "application/json")
.setHeader("Authorization", "Basic BASE64APIKEY")
.setBody("{\n \"usage\": {\n \"quantity\": 1000,\n \"price_point_id\": \"149416\",\n \"memo\": \"My memo\"\n }\n}")
.execute()
.toCompletableFuture()
.thenAccept(System.out::println)
.join();
client.close();
// Create Usage - https://developers.chargify.com/docs/api-docs/9954fb40d117d-create-usage
var client = new HttpClient();
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri("https://subdomain.chargify.com/subscriptions/subscription_id/components/component_id/usages.json"),
Headers =
{
{ "Content-Type", "application/json" },
{ "Authorization", "Basic BASE64APIKEY" },
},
Content = new StringContent("{\n \"usage\": {\n \"quantity\": 1000,\n \"price_point_id\": \"149416\",\n \"memo\": \"My memo\"\n }\n}")
{
Headers =
{
ContentType = new MediaTypeHeaderValue("application/json")
}
}
};
using (var response = await client.SendAsync(request))
{
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}
{
"usage": {
"id": 138522957,
"memo": "My memo",
"created_at": "2017-11-13T10:05:32-06:00",
"price_point_id": 149416,
"quantity": 1000,
"component_id": 500093,
"component_handle": "handle",
"subscription_id": 22824464
}
}
Server-side
Next, you'll need to put the call in the right place inside your code, and make it dynamic for all subscription_id
values and component_id
values. The general structure is:
- Your scheduled cron job is initialized at a specific time (eg: nightly at midnight).
- It knows the last record number (typically a row ID in your database) or timestamp where it last was processed. It picks up where it left off.
- It reads through each record (typically in your database) and sends the usage API request to Maxio.
Create several subscriptions, each for your real use cases with usage. Simulate action in your app that triggers your usage workflow.
- Set your batch process to be triggered (so you don't have to wait until the real time).
- Test that the usage is immediately sent to Maxio.
- Ensure that the usage goes to the proper subscriptions.
- Once 1-3 work, turn on your scheduled job, and let it run naturally. Simulate some usage in your app, and after your scheduled job's run time, verify it worked.
- See that your usage is rated as you intended. You can create a component with a 1 day billing interval or use the Proforma Invoice.