Tracking free trials
This tutorial describes how to track free trials in ChartMogul using the API.
Tracking trials lets you measure how many trials start, understand which plans customers are trialing, and analyze how effectively trials convert into free or paid subscriptions.
The recommended way to track a free trial is to create a trial line item associated with a plan.
To track a free trial:
- Create or identify the plan being trialed
- Create an invoice containing a
trialline item - When the customer converts, import their paid subscription
Gradual rollout.
We introduced the trial line item type on October 2, 2025, and are making it gradually available to all accounts. Learn more.
Creating a plan
Every trial must be associated with a plan.
If the plan does not already exist in ChartMogul, create it using the Create a Plan endpoint:
- Provide the
data_source_uuidof the source that stores your subscription data. - Specify the
nameandexternal_idfor the plan. - Set the
interval_unitandinterval_count. For example, a monthly plan has aninterval_unitofmonthand aninterval_countof1.
curl -X POST "https://api.chartmogul.com/v1/plans" \
-u YOUR_API_KEY: \
-H "Content-Type: application/json" \
-d '{
"data_source_uuid": "ds_fef05d54-47b4-431b-aed2-eb6b9e545430",
"name": "Pro",
"interval_count": 1,
"interval_unit": "month",
"external_id": "plan_pro"
}'
ChartMogul::Import::Plan.create!(
data_source_uuid: "ds_fef05d54-47b4-431b-aed2-eb6b9e545430",
name: "Pro",
interval_count: 1,
interval_unit: "month",
external_id: "plan_pro",
)
ChartMogul.Plan.create(config, {
data_source_uuid: "ds_fef05d54-47b4-431b-aed2-eb6b9e545430",
name: "Pro",
interval_count: 1,
interval_unit: "month",
external_id: "plan_pro",
});
ChartMogul\Import\Plan::create([
"data_source_uuid" => "ds_fef05d54-47b4-431b-aed2-eb6b9e545430",
"name" => "Pro",
"interval_count" => 1,
"interval_unit" => "month",
"external_id" => "plan_pro"
]);
api.CreatePlan(&cm.Plan{
DataSourceUUID: "ds_fef05d54-47b4-431b-aed2-eb6b9e545430",
Name: "Pro",
IntervalCount: 1,
IntervalUnit: "month",
ExternalID: "plan_pro",
})
chartmogul.Plan.create(
config,
data={
"data_source_uuid": "ds_fef05d54-47b4-431b-aed2-eb6b9e545430",
"name": "Pro",
"interval_count": 1,
"interval_unit": "month",
"external_id": "plan_pro",
},
)
Use the plan_uuid from the response when creating the trial. If the plan already exists, use its existing plan_uuid.
Creating a free trial
Use the Import Invoices endpoint to create an invoice containing a trial line item:
- Set the
typetotrial. - Set the
amount_in_centsto0. - Set the
plan_uuidto the UUID of the plan being trialed. - Use a unique
subscription_external_idto identify the trial subscription. - Set the
service_period_startto the date the trial begins. - Set the
service_period_endto the date the trial ends.
If the customer belongs to a Stripe, Chargebee, Recurly, Braintree, Google Play, App Store Connect or SaaSync source, set the handle_as_user_edit query parameter to true. Without it, the invoice may be deleted during a reprocess or reimport. This is not required for custom sources. Learn more.
For example, the following creates a 14-day free trial of the Pro plan:
curl -X POST "https://api.chartmogul.com/v1/import/customers/cus_f466e33d-ff2b-4a11-8f85-417eb02157a7/invoices" \
-u YOUR_API_KEY: \
-H "Content-Type: application/json" \
-d '{
"invoices": [
{
"external_id": "inv_trial_001",
"date": "2026-01-10",
"currency": "USD",
"line_items": [
{
"type": "trial",
"external_id": "trial_001",
"subscription_external_id": "sub_pro_001",
"plan_uuid": "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
"service_period_start": "2026-01-10",
"service_period_end": "2026-01-24",
"amount_in_cents": 0,
"quantity": 1
}
]
}
]
}'
# The Ruby library has no dedicated trial line item class yet,
# so override the type on a subscription line item.
line_item = ChartMogul::LineItems::Subscription.new(
external_id: "trial_001",
subscription_external_id: "sub_pro_001",
plan_uuid: "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
service_period_start: Time.utc(2026, 1, 10),
service_period_end: Time.utc(2026, 1, 24),
amount_in_cents: 0,
quantity: 1,
)
line_item.type = "trial"
invoice = ChartMogul::Invoice.new(
external_id: "inv_trial_001",
date: Time.utc(2026, 1, 10),
currency: "USD",
line_items: [line_item],
)
ChartMogul::CustomerInvoices.create!(
customer_uuid: "cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
invoices: [invoice],
)
ChartMogul.Invoice.create(config, "cus_f466e33d-ff2b-4a11-8f85-417eb02157a7", {
invoices: [
{
external_id: "inv_trial_001",
date: "2026-01-10",
currency: "USD",
line_items: [
{
type: "trial",
external_id: "trial_001",
subscription_external_id: "sub_pro_001",
plan_uuid: "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
service_period_start: "2026-01-10",
service_period_end: "2026-01-24",
amount_in_cents: 0,
quantity: 1,
},
],
},
],
});
// The PHP library has no dedicated trial line item class yet,
// so override the type on a subscription line item.
$line_item = new ChartMogul\LineItems\Subscription([
"type" => "trial",
"external_id" => "trial_001",
"subscription_external_id" => "sub_pro_001",
"plan_uuid" => "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
"service_period_start" => "2026-01-10",
"service_period_end" => "2026-01-24",
"amount_in_cents" => 0,
"quantity" => 1
]);
$invoice = new ChartMogul\Invoice([
"external_id" => "inv_trial_001",
"date" => "2026-01-10",
"currency" => "USD",
"line_items" => [$line_item]
]);
ChartMogul\CustomerInvoices::create([
"customer_uuid" => "cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
"invoices" => [$invoice]
]);
lineItem := &cm.LineItem{
Type: "trial",
ExternalID: "trial_001",
SubscriptionExternalID: "sub_pro_001",
PlanUUID: "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
ServicePeriodStart: "2026-01-10",
ServicePeriodEnd: "2026-01-24",
AmountInCents: 0,
Quantity: 1,
}
invoice := &cm.Invoice{
ExternalID: "inv_trial_001",
Date: "2026-01-10",
Currency: "USD",
LineItems: []*cm.LineItem{lineItem},
}
api.CreateInvoices(
[]*cm.Invoice{invoice},
"cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
)
chartmogul.Invoice.create(
config,
uuid="cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
data={
"invoices": [
{
"external_id": "inv_trial_001",
"date": datetime(2026, 1, 10),
"currency": "USD",
"line_items": [
{
"type": "trial",
"external_id": "trial_001",
"subscription_external_id": "sub_pro_001",
"plan_uuid": "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
"service_period_start": datetime(2026, 1, 10),
"service_period_end": datetime(2026, 1, 24),
"amount_in_cents": 0,
"quantity": 1,
}
],
}
]
},
)
ChartMogul now records the customer as having started a free trial of the Pro plan on January 10.
A customer can have multiple trials. Each trial can be associated with a different plan and have its own start and end dates.
Paid trials.
You can also use trial line items to track paid trials. Set amount_in_cents to a value greater than zero.
Converting a trial to a paid subscription
When the customer converts, use the Import Invoices endpoint to import their first paid invoice in the same way you normally import subscription data:
- Use a
subscriptionline item rather than atrialline item. - Use the same
subscription_external_idas the trial if the paid subscription is a continuation of the same subscription. - For paid subscriptions, a
service_period_endis required. - If the customer belongs to a Stripe, Chargebee, Recurly, Braintree, Google Play, App Store Connect or SaaSync source, set the
handle_as_user_editquery parameter totrue.
curl -X POST "https://api.chartmogul.com/v1/import/customers/cus_f466e33d-ff2b-4a11-8f85-417eb02157a7/invoices" \
-u YOUR_API_KEY: \
-H "Content-Type: application/json" \
-d '{
"invoices": [
{
"external_id": "inv_paid_001",
"date": "2026-01-24",
"currency": "USD",
"line_items": [
{
"type": "subscription",
"external_id": "line_paid_001",
"subscription_external_id": "sub_pro_001",
"plan_uuid": "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
"service_period_start": "2026-01-24",
"service_period_end": "2026-02-24",
"amount_in_cents": 5000,
"quantity": 1
}
]
}
]
}'
line_item = ChartMogul::LineItems::Subscription.new(
external_id: "line_paid_001",
subscription_external_id: "sub_pro_001",
plan_uuid: "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
service_period_start: Time.utc(2026, 1, 24),
service_period_end: Time.utc(2026, 2, 24),
amount_in_cents: 5000,
quantity: 1,
)
invoice = ChartMogul::Invoice.new(
external_id: "inv_paid_001",
date: Time.utc(2026, 1, 24),
currency: "USD",
line_items: [line_item],
)
ChartMogul::CustomerInvoices.create!(
customer_uuid: "cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
invoices: [invoice],
)
ChartMogul.Invoice.create(config, "cus_f466e33d-ff2b-4a11-8f85-417eb02157a7", {
invoices: [
{
external_id: "inv_paid_001",
date: "2026-01-24",
currency: "USD",
line_items: [
{
type: "subscription",
external_id: "line_paid_001",
subscription_external_id: "sub_pro_001",
plan_uuid: "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
service_period_start: "2026-01-24",
service_period_end: "2026-02-24",
amount_in_cents: 5000,
quantity: 1,
},
],
},
],
});
$line_item = new ChartMogul\LineItems\Subscription([
"external_id" => "line_paid_001",
"subscription_external_id" => "sub_pro_001",
"plan_uuid" => "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
"service_period_start" => "2026-01-24",
"service_period_end" => "2026-02-24",
"amount_in_cents" => 5000,
"quantity" => 1
]);
$invoice = new ChartMogul\Invoice([
"external_id" => "inv_paid_001",
"date" => "2026-01-24",
"currency" => "USD",
"line_items" => [$line_item]
]);
ChartMogul\CustomerInvoices::create([
"customer_uuid" => "cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
"invoices" => [$invoice]
]);
lineItem := &cm.LineItem{
Type: "subscription",
ExternalID: "line_paid_001",
SubscriptionExternalID: "sub_pro_001",
PlanUUID: "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
ServicePeriodStart: "2026-01-24",
ServicePeriodEnd: "2026-02-24",
AmountInCents: 5000,
Quantity: 1,
}
invoice := &cm.Invoice{
ExternalID: "inv_paid_001",
Date: "2026-01-24",
Currency: "USD",
LineItems: []*cm.LineItem{lineItem},
}
api.CreateInvoices(
[]*cm.Invoice{invoice},
"cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
)
chartmogul.Invoice.create(
config,
uuid="cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
data={
"invoices": [
{
"external_id": "inv_paid_001",
"date": datetime(2026, 1, 24),
"currency": "USD",
"line_items": [
{
"type": "subscription",
"external_id": "line_paid_001",
"subscription_external_id": "sub_pro_001",
"plan_uuid": "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
"service_period_start": datetime(2026, 1, 24),
"service_period_end": datetime(2026, 2, 24),
"amount_in_cents": 5000,
"quantity": 1,
}
],
}
]
},
)
ChartMogul treats the trial and paid subscription as a continuous subscription history and includes the conversion in trial conversion reporting.
When the paid subscription comes from Stripe or another billing system
In many cases, you may track the free trial using the API while the customer's paid subscription is later imported automatically from a billing system such as Stripe or Recurly.
Because the trial and paid subscription come from different sources, ChartMogul initially creates a separate customer record for the paid subscription.
Set up an automation to merge the new customer record into the customer's existing trial record:
- Trigger: Customer classified as duplicate. Match the External ID of the new customer record with the External ID of the existing record. This requires using the same customer
external_idin both sources. You can also match on another property, such as email. - Condition: Source is one of your billing system sources, for example Recurly.
- Action: Merge duplicate customer(s).
Converting a trial to a free subscription
A trial can also convert to a free subscription. In this case, use the Import Invoices endpoint to create a subscription line item with amount_in_cents set to 0:
- Use the same
subscription_external_idas the trial to treat the conversion as a continuation of the same subscription. - A
service_period_endis optional for free subscriptions. Omit it for open-ended free subscriptions that remain active until the customer upgrades or cancels. - If the customer belongs to a Stripe, Chargebee, Recurly, Braintree, Google Play, App Store Connect or SaaSync source, set the
handle_as_user_editquery parameter totrue.
curl -X POST "https://api.chartmogul.com/v1/import/customers/cus_f466e33d-ff2b-4a11-8f85-417eb02157a7/invoices" \
-u YOUR_API_KEY: \
-H "Content-Type: application/json" \
-d '{
"invoices": [
{
"external_id": "inv_free_001",
"date": "2026-01-24",
"currency": "USD",
"line_items": [
{
"type": "subscription",
"external_id": "line_free_001",
"subscription_external_id": "sub_pro_001",
"plan_uuid": "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
"service_period_start": "2026-01-24",
"amount_in_cents": 0,
"quantity": 1
}
]
}
]
}'
line_item = ChartMogul::LineItems::Subscription.new(
external_id: "line_free_001",
subscription_external_id: "sub_pro_001",
plan_uuid: "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
service_period_start: Time.utc(2026, 1, 24),
amount_in_cents: 0,
quantity: 1,
)
invoice = ChartMogul::Invoice.new(
external_id: "inv_free_001",
date: Time.utc(2026, 1, 24),
currency: "USD",
line_items: [line_item],
)
ChartMogul::CustomerInvoices.create!(
customer_uuid: "cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
invoices: [invoice],
)
ChartMogul.Invoice.create(config, "cus_f466e33d-ff2b-4a11-8f85-417eb02157a7", {
invoices: [
{
external_id: "inv_free_001",
date: "2026-01-24",
currency: "USD",
line_items: [
{
type: "subscription",
external_id: "line_free_001",
subscription_external_id: "sub_pro_001",
plan_uuid: "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
service_period_start: "2026-01-24",
amount_in_cents: 0,
quantity: 1,
},
],
},
],
});
$line_item = new ChartMogul\LineItems\Subscription([
"external_id" => "line_free_001",
"subscription_external_id" => "sub_pro_001",
"plan_uuid" => "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
"service_period_start" => "2026-01-24",
"amount_in_cents" => 0,
"quantity" => 1
]);
$invoice = new ChartMogul\Invoice([
"external_id" => "inv_free_001",
"date" => "2026-01-24",
"currency" => "USD",
"line_items" => [$line_item]
]);
ChartMogul\CustomerInvoices::create([
"customer_uuid" => "cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
"invoices" => [$invoice]
]);
lineItem := &cm.LineItem{
Type: "subscription",
ExternalID: "line_free_001",
SubscriptionExternalID: "sub_pro_001",
PlanUUID: "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
ServicePeriodStart: "2026-01-24",
AmountInCents: 0,
Quantity: 1,
}
invoice := &cm.Invoice{
ExternalID: "inv_free_001",
Date: "2026-01-24",
Currency: "USD",
LineItems: []*cm.LineItem{lineItem},
}
api.CreateInvoices(
[]*cm.Invoice{invoice},
"cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
)
chartmogul.Invoice.create(
config,
uuid="cus_f466e33d-ff2b-4a11-8f85-417eb02157a7",
data={
"invoices": [
{
"external_id": "inv_free_001",
"date": datetime(2026, 1, 24),
"currency": "USD",
"line_items": [
{
"type": "subscription",
"external_id": "line_free_001",
"subscription_external_id": "sub_pro_001",
"plan_uuid": "pl_3eb4efb2-d101-4dce-a664-be271b0da4de",
"service_period_start": datetime(2026, 1, 24),
"amount_in_cents": 0,
"quantity": 1,
}
],
}
]
},
)
See Tracking free subscriptions for more information about importing and managing free subscriptions.
Trial reporting in ChartMogul
Once imported, trial data can be used throughout ChartMogul. You can use it to:
- Track the number of free trials started over time in the Free Trials chart.
- See which customers are currently trialing your product using the Trial status.
- Segment customers based on their trial status.
- Analyze which plans customers trial.
- Measure trial-to-paid conversion in the Trial-to-Paid Conversion Rate chart.
- Measure trial-to-free-or-paid conversion in the Trial-to-Free-or-Paid Conversions chart.
- Analyze how long customers take to convert.
Because each trial is associated with a subscription and plan, ChartMogul can distinguish between different trials for the same customer and provide plan-level trial reporting.
Tracking trials using free_trial_started_at
ChartMogul also supports a simpler customer-level method for recording the start of a free trial. Set free_trial_started_at when creating or updating a customer:
curl -X PATCH "https://api.chartmogul.com/v1/customers/cus_ab223d54-75b4-431b-adb2-eb6b9e234571" \
-u YOUR_API_KEY: \
-H "Content-Type: application/json" \
-d '{
"free_trial_started_at": "2026-01-10T00:00:00Z"
}'
customer = ChartMogul::Customer.retrieve("cus_ab223d54-75b4-431b-adb2-eb6b9e234571")
customer.free_trial_started_at = Time.utc(2026, 1, 10)
customer.update!
ChartMogul.Customer.modify(config, "cus_ab223d54-75b4-431b-adb2-eb6b9e234571", {
free_trial_started_at: "2026-01-10T00:00:00Z",
});
ChartMogul\Customer::update(
["customer_uuid" => "cus_ab223d54-75b4-431b-adb2-eb6b9e234571"],
["free_trial_started_at" => "2026-01-10T00:00:00Z"]
);
api.UpdateCustomer(&cm.Customer{
FreeTrialStartedAt: "2026-01-10T00:00:00Z",
}, "cus_ab223d54-75b4-431b-adb2-eb6b9e234571")
chartmogul.Customer.modify(
config,
uuid="cus_ab223d54-75b4-431b-adb2-eb6b9e234571",
data={"free_trial_started_at": "2026-01-10T00:00:00Z"},
)
This records a single trial start date for the customer. In the same way, set lead_created_at to record when the customer became a lead.
For new implementations, we recommend using trial line items instead. Trial line items allow ChartMogul to track:
- The plan being trialed
- The trial start and end dates
- Multiple trials for the same customer
- Free and paid trials
- Trial status
- Trial-to-subscription conversions
Use free_trial_started_at when you only need to record that a customer started a trial and don't need subscription-level trial data.