I’ve always been kinda intrigued by the idea of parallel universes, the idea that there are infinite copies of the universe, with myself and all the people I care about, but each with slight differences to the universe I inhabit.
The ChargerS module provides the Butterfly Effect needed to create infinite instances of our CGrateS events, each with subtle differences.
Typically if you’re charging subscribers for calls, someone else (or multiple someones) may charge you for those calls, for example you charge your subscribers for an outbound call, but other carriers you interconnect with will charge you for terminating those calls to their subscribers, and for incoming calls you may want to charge the other carriers that terminate calls into your network.
By defining in CGrates what your suppliers charge you, or what you charge suppliers, or reseller rates, or commissions, or any other varied call charge, allows us to:
- See profit on each call
- Understand supplier costs
- Enable reselling at different rates
- Opens the door to Least-Cost Routing (Without knowing the cost, we can’t find the cheapest)
- Ensure you don’t have calls where you make a loss (Supplier charged you more than you charged the customer)
So how do we do this?
Well, we do this with ChargerS.
When I first looked at CGrateS, the ChargerS module seems like an extra step that did nothing,
In ngrep
you’d see the ChargerSv1.ProcessEvent
request, and the response, but it doesn’t really do anything, and it’s a PITA when you don’t have a Charger defined and everything stops working.
I’ve spoken a lot about SIP on this blog, and I’m going to assume some level of familiarity with telephony since we’re talking about CGrateS (which is mostly used for telephony), but the best concept I can relate ChargerS to is Serial Forking in SIP, but for the CGrateS event.
A single CGrateS “event” (JSON RPC) comes into CGrateS from wherever, but with ChargerS, we can fork that single event into multiple CGrateS events, which are all treated as unique events.
This is where it starts to get interesting, let’s say we want to calculate a supplier cost and a retail cost, well, with ChargerS we define a rule for supplier and a rule for retail, one single event comes into CGrateS, but with ChargerS setup to create a retail and supplier event, then there are now two events inside CGrateS, one for the supplier and one for the retail.
First we’ll define a default boring charger:
{
"method": "APIerSv1.SetChargerProfile",
"params": [
{
"ID": "CHARGER_Default",
'FilterIDs': [],
'AttributeIDs' : ['*none'],
'RunID' : 'default',
'Weight': 0,
}
] }
Alright, so far so good, but now we’ll define a second charger, and this one will be for calculating the retail rate for a call.
{
"method": "APIerSv1.SetChargerProfile",
"params": [
{
"ID": "CHARGER_Retail",
"FilterIDs": [],
'AttributeIDs' : ['*constant:*req.Category:RetailCharge'],
'RunID' : 'charger_retail',
'Weight': 0,
}
] }
So what did we just do?
Well, now when the ChargerSv1.ProcessEvent
request hits chargers two events will come out and get processed by the rest of CGrateS as if they’re unique events / calls to be rated.
We’ve cloned our event, now we’ve got two copies of the same event.

The first copy (the original event), will be treated exactly as it is now, the other will see a new event generated inside CGrateS, it’ll be a copy of the original event, except for a few minor changes.
Let’s take a look at what happens to our event going through ChargerS when we generate a CDRsV2.ProcessExternalCDR
API request:
{
"method": "ChargerSv1.ProcessEvent",
"params": [
{
"Tenant": "cgrates.org",
"ID": "2645818",
"Time": null,
"Event": {
"Account": "Nick_Test_123",
"AnswerTime": "2024-12-26T12:34:44+11:00",
"CGRID": "18d3e23ac3727474539f29cc11694cac11fb5e32",
"OriginID": "95fff282-c329-11ef-8e4e-98fa9b127b52",
"RunID": "*default",
...
"Subject": "Nick_Test_123",
"Tenant": "cgrates.org",
"ToR": "*voice",
"Usage": 150000000000
},}],"id": 20
}
But now let’s look at what comes out of this request to ChargerS:
{
"id": 20,
"result": [
{
"ChargerSProfile": "DEFAULT",
"AttributeSProfiles": null,
"AlteredFields": [
"*req.RunID"
],
"CGREvent": {
"ID": "5fd2d6a",
"Event": {
"Account": "Nick_Test_123",
"AnswerTime": "2024-12-26T12:44:40+11:00",
"CGRID": "3c01050a3f49fb215e318523dcd4255797d50145",
"Category": "call",
"RunID": "default",
}, }
},{
"ChargerSProfile": "CHARGER_Retail",
"AttributeSProfiles": [
"*constant:*req.Category:RetailCharge"
],
"AlteredFields": [
"*req.RunID",
"*req.Category"
],
"CGREvent": {
"ID": "5fd2d6a",
"Event": {
"Account": "Nick_Test_123",
"AnswerTime": "2024-12-26T12:44:40+11:00",
"CGRID": "3c01050a3f49fb215e318523dcd4255797d50145",
"Category": "RetailCharge",
"RunID": "charger_retail",
},
} } ], "error": null
}
I’ve tried to keep the above example as minimal as possible, but if we have a look we can now see two events, the first is our default charger, where nothing is changed; it’s got the same category as we set on the ProcessExternalCDR
request (call) and the RunID is “default” per the default charger.
But look below and we’ve got another copy, this time the RunID is set to charger_retail
, because that’s what we’ve set it to inside the RunID parameter for the charger named CHARGER_Retail
, this means when filtering CDRs we’ll be able to spot these ones really easily, and know it’s a fork of a different event.
But importantly we’ve changed some of the values in the CGrateS Event, the same way AttributeS changes stuff.
So what have we changed? Well the Category
of the new request is now RetailCharge.
Now if we cast our mind back to setting the RatingProfile back in Tutorial 3, you may remember we set the Category on the RatingProfile.
Now is when this matters. By setting different Categories in our Rating Profile, we can create a new RatingProfile, with the category set to RetailCharge, but referencing a whole different RatingPlan, with different destinations and rates, and this second event that was forked by ChargerS, will match that RatingProfile, and the RatingPlans that go with it.
For everything matching we’ll get two CDRs (if we’re calling *cdrs
that is) and they’re treated as totally separate records.
Think about it; by defining a new RatingProfile with category Wholesale
with your wholesale rate, and then creating a Charger for that category, you’ll have a retail CDR and a wholesale CDR. Same for reseller rates, commissions, anything!
We’re using this in one of our networks to handle rating for all the SMS traffic, we’ve got various suppliers and sources for A2P and P2P traffic, and having additional chargers to calculate different rates in a different currency for billing our suppliers is super useful.
#Second charger used for calculating the A2P charge for SMS in USD
print(CGRateS_Obj_local.SendData({
"method": "APIerSv1.SetChargerProfile",
"params": [
{
"ID": "CHARGER_SMS_A2P",
"FilterIDs": ["*string:~*req.Category:sms", "*notstring:~*req.Account:gsm_0340"],
'AttributeIDs' : ['*constant:*req.RequestType:*rated;*constant:*req.Category:sms_a2p'],
'RunID' : 'charger_a2p',
'Weight': 0,
}
] } ))