Online Charging System (OCS) Integration
Comprehensive guide to OmniTAS integration with Online Charging Systems via Diameter Ro interface, including real-time credit control, AVP extraction, and FreeSWITCH variable mapping.
Table of Contents
- Architecture Overview
- What Controls Whether a Call Is Charged
- Credit Control Flow
- AVP Parsing and Variable Mapping
- Configuration
- FreeSWITCH Integration
- Answer & Hangup Notifications
- Diameter Messages
- Metrics
- Troubleshooting
- Reference
Architecture Overview
OmniTAS implements the Diameter Ro interface per 3GPP TS 32.299 for real-time online charging. The system authorizes calls by requesting credit from an OCS before call setup, monitors credit during the call, and reports final usage on termination.
Key Components
Credit-Control-Request (CCR):
- CCR-Initial (Type 1): Sent before call setup to request initial credit authorization
- CCR-Update (Type 2): Sent during active calls for re-authorization or interim updates
- CCR-Terminate (Type 3): Sent on call termination with final usage reporting
Credit-Control-Answer (CCA):
- Contains granted service units (time quota in seconds)
- Includes vendor-specific AVPs with additional charging data
- Provides routing information, charged party details, and service identifiers
What Controls Whether a Call Is Charged
A call produces Diameter CCRs only if all three of the following conditions hold. They are evaluated when OmniTAS builds the Mobile Originating (MO) dialplan for the call:
- Online charging is enabled —
online_charging.enabledistrue. Whenfalse, OmniTAS authorizes every call locally and never contacts the OCS. - The call is Mobile Originating (MO) — only the MO leg is charged. Mobile Terminating (MT) calls are never charged: the MT path does not perform OCS authorization, so no credit-control session is created for it.
- The destination is not exempt — the dialled number does not match any pattern in
skipped_regex(e.g. emergency numbers, service codes).
If all three hold, OmniTAS sends a CCR-Initial and opens a credit-control session keyed by the SIP Call-ID. From that point, CCR-Update and CCR-Terminate are driven by answer/hangup events (see Answer & Hangup Notifications) and are only generated for call legs that have an open session. MT legs, bridged B-legs, and exempt calls therefore never produce CCRs.
Note: Reaching the OCS does not guarantee the call connects. A successful authorization with zero granted units bars the call — see Credit Exhaustion and Call Barring. Barring happens after a CCR is sent; it is distinct from the conditions above, which decide whether a CCR is sent at all.
Credit Control Flow
Call Authorization Sequence
Credit Exhaustion and Call Barring
Call barring at setup. If the OCS returns a successful CCA (Result-Code 2001) but with
zero granted units — or an explicit denial such as 4012 (DIAMETER_CREDIT_LIMIT_REACHED)
or 4010 (DIAMETER_END_USER_SERVICE_DENIED) — OmniTAS treats the call as having no credit and
bars it with hangup cause OUTGOING_CALL_BARRED. The subscriber never connects. This is the
expected outcome for an unprovisioned or out-of-credit subscriber.
Mid-call exhaustion. If credit is granted at setup but later runs out (a periodic
CCR-Update returns zero units or a credit-limit error), OmniTAS terminates the in-progress
call. With credit_exhaustion_announcement configured it transfers the caller to an
announcement before hangup; otherwise it hangs up directly.
CCR-Terminate fires at the exhaustion moment, before any cleanup. When the balance is exhausted, OmniTAS sends the CCR-Terminate immediately (charged answer→exhaustion), then plays the out-of-credit announcement or hangs up. This is deliberate: the time the subscriber spends listening to the "you're out of balance" prompt is not chargeable, so the CDR must end when credit ran out — not when the user hangs up from the recording. The session terminates at that point, so the eventual FreeSWITCH hangup for the call finds no session and sends nothing (idempotent — no second CCR-T). This holds whether cleanup is a transfer-to-announcement or a direct hangup, so it does not depend on the FreeSWITCH hangup cause.
OmniTAS supports multiple mechanisms for handling credit exhaustion, with automatic integration between scheduled hangups and credit exhaustion announcements.
Scheduled Hangup with Dynamic Rescheduling
When schedule_hangup_auth is enabled, OmniTAS schedules a FreeSWITCH timer that automatically terminates calls when granted credit expires. This timer is dynamically rescheduled every time new credit is granted via CCR-Update responses.
How it works:
Buffer Logic:
OmniTAS sends CCR-Update messages before the granted credit expires to ensure continuous service. The buffer time is configurable via ccr_update_buffer_seconds (default: 2 seconds).
Example timeline:
- T+0s: Call answered, OCS grants 10s, timer scheduled for T+10s
- T+8s: CCR-U sent (10s - 2s buffer)
- T+8.1s: OCS grants 10s, timer rescheduled to T+18.1s (10s from now)
- T+16.1s: CCR-U sent
- T+16.2s: OCS grants 10s, timer rescheduled to T+26.2s
- Call continues as long as OCS keeps granting credit
Logs to watch:
[OCS HANGUP RESCHEDULE] Found UUID <uuid> for call <id> - rescheduling timer to 10s from now
[SCHED TRANSFER] Scheduling transfer to credit_exhausted dialplan for <uuid> in 10s
[OCS HANGUP RESCHEDULE] Successfully rescheduled timer for call <id> (UUID: <uuid>)
Integration: schedule_hangup_auth + credit_exhaustion_announcement
When both features are enabled, OmniTAS automatically uses scheduled transfers instead of direct hangups, allowing the caller to hear an announcement before call termination.
Without announcement configured:
config :tas, :online_charging,
schedule_hangup_auth: true,
credit_exhaustion_announcement: nil
→ Uses sched_hangup - direct hangup when credit expires
With announcement configured:
config :tas, :online_charging,
schedule_hangup_auth: true,
credit_exhaustion_announcement: "${base_dir}/sounds/en/us/callie/misc/8000/credit_exhausted.wav"
→ Uses sched_transfer - transfers to credit_exhausted dialplan which plays announcement then hangs up
How the transfer works:
- OmniTAS sets
tas_call_reason=credit_exhaustedchannel variable - Schedules transfer to
credit_exhaustedextension inims_asdialplan context - When timer fires:
- FreeSWITCH transfers A-leg to credit_exhausted dialplan
- Bridge breaks automatically, B-leg receives BYE
- Dialplan plays announcement to A-leg
- Call terminates after announcement
Benefits:
- Caller hears professional announcement instead of abrupt disconnect
- B-leg (called party) doesn't hear announcement
- CCR-T still sent with actual usage
- Announcement path: Must be relative to FreeSWITCH base directory (use
${base_dir}variable)
Immediate Credit Exhaustion During CCR-Update
If the OCS denies credit or returns zero seconds during a CCR-Update, OmniTAS immediately triggers credit exhaustion handling, overriding any scheduled timer.
OCS Response Scenarios:
Handled Error Codes:
| OCS Response | Action | Logs |
|---|---|---|
{:ok, 0} (Zero seconds) | Immediate credit exhaustion hangup | Credit exhausted (zero seconds allocated) - triggering immediate hangup |
{:error, 4012} (CREDIT_LIMIT_REACHED) | Immediate credit exhaustion hangup | Credit exhausted (4012 CREDIT_LIMIT_REACHED) - triggering immediate hangup |
{:error, 4010} (END_USER_SERVICE_DENIED) | Immediate credit exhaustion hangup | Service denied (4010 END_USER_SERVICE_DENIED) - triggering immediate hangup |
{:error, reason} (Other errors) | Stop periodic CCR job, scheduled timer fires | Periodic CCR failed with error <reason> - Stopping job |
{:ok, N} where N > 0 | Reschedule timer to +N seconds | Periodic CCA allocated Ns, will send next CCR-U in (N-buffer)s |
Priority: Immediate credit exhaustion handling wins over scheduled timer. If OCS denies credit at T+8s but timer was scheduled for T+10s, the immediate hangup at T+8s occurs and the scheduled timer becomes irrelevant.
Example timeline with mid-call credit denial:
T+0s: Call answered
T+0.1s: OCS grants 10s → Timer scheduled for T+10.1s
T+8s: CCR-U sent (buffer = 2s)
T+8.1s: OCS returns 0 seconds → Immediate transfer to credit_exhausted dialplan
T+8.2s: Announcement plays to caller
T+10s: Call terminated (scheduled timer irrelevant)
Logs for immediate credit exhaustion:
[warning] Credit exhausted (zero seconds allocated) - triggering immediate hangup
[warning] Hanging up call <id> (UUID: <uuid>) due to credit exhaustion
[info] Credit exhaustion announcement config: "${base_dir}/sounds/..."
[info] Playing announcement before hangup: ...
[info] Setting tas_call_reason=credit_exhausted for <uuid>
[info] Transferring to credit exhausted dialplan: uuid_transfer <uuid> credit_exhausted XML ims_as
Summary: Credit Exhaustion Mechanisms
OmniTAS provides two complementary mechanisms:
-
Scheduled Timer (
schedule_hangup_auth):- Automatic hangup/transfer when granted credit expires
- Dynamically rescheduled on each CCR-U response
- Uses buffer logic to send CCR-U before expiration
- Integrates with announcement feature
-
Immediate Exhaustion Handling:
- Triggered when OCS denies credit during CCR-U
- Overrides scheduled timer
- Supports announcement playback
- Handles specific Diameter error codes
Both mechanisms respect the credit_exhaustion_announcement configuration and will play the configured audio before terminating calls when configured.
AVP Parsing and Variable Mapping
Overview
OmniTAS automatically extracts Attribute-Value Pairs (AVPs) from Credit-Control-Answer messages and makes them available to FreeSWITCH as channel variables. This enables dialplan logic to use OCS-provided data for routing decisions, billing purposes, or call treatment.
Supported AVP Types:
- Simple values (UTF8String, Unsigned32, Integer32)
- Grouped AVPs with nested structures
- Vendor-specific AVPs (e.g., 3GPP Service-Information)
Variable Naming Convention:
AVPs are flattened into dot-notation channel variables with the prefix CCA:
CCA.<AVP-Name>.<Nested-AVP-Name>.<Value-AVP-Name> = "value"
Common AVP Mappings
Service-Information AVP (3GPP)
The Service-Information grouped AVP (AVP Code 873, Vendor-ID 10415) contains IMS-specific charging details:
Example OCS Response:
Service-Information
├── IMS-Information
│ ├── Carrier-Select-Routing-Information: "1408"
│ └── Node-Functionality: 6
└── Alternate-Charged-Party-Address: "NickTest"
Resulting FreeSWITCH Variables:
CCA.Service-Information.Carrier-Select-Routing-Information = "1408"
CCA.Service-Information.Alternate-Charged-Party-Address = "NickTest"
Accessing in Dialplan: Variables use dot notation and hyphens as shown above:
<action application="log" data="INFO Carrier: ${CCA.Service-Information.Carrier-Select-Routing-Information}"/>
Viewing with uuid_dump:
In FreeSWITCH console or ESL, variables appear with the variable_ prefix:
variable_CCA.Service-Information.Carrier-Select-Routing-Information: 1408
variable_CCA.Service-Information.Alternate-Charged-Party-Address: NickTest
Note: FreeSWITCH preserves dots and hyphens in variable names. The variables work in all dialplan contexts and applications.
Granted-Service-Unit AVP
Time quotas are extracted and made available:
OCS Response:
Granted-Service-Unit
└── CC-Time: 600
Variable:
allocated_time = 600
AVP Processing Logic
Processing Rules:
- Grouped AVPs add a level to the variable name hierarchy but have no value themselves
- Simple AVPs are mapped to variables with their full dotted path
- Vendor-Specific AVPs are processed identically to standard AVPs
- Unknown AVPs are safely skipped without errors
Example: Multi-Level Nesting
OCS CCA Structure:
Service-Information (Grouped)
├── IMS-Information (Grouped)
│ ├── Node-Functionality: 6
│ ├── Role-Of-Node: 1
│ ├── Calling-Party-Address: "tel:+313380000000670"
│ └── Time-Stamps (Grouped)
│ ├── SIP-Request-Timestamp: "2026-01-24T22:40:18Z"
│ └── SIP-Response-Timestamp: "2026-01-24T22:40:18Z"
└── IN-Information (Grouped)
└── Real-Called-Number: "24724741234"
FreeSWITCH Variables Created:
CCA.Service-Information.IMS-Information.Node-Functionality = "6"
CCA.Service-Information.IMS-Information.Role-Of-Node = "1"
CCA.Service-Information.IMS-Information.Calling-Party-Address = "tel:+313380000000670"
CCA.Service-Information.IMS-Information.Time-Stamps.SIP-Request-Timestamp = "2026-01-24T22:40:18Z"
CCA.Service-Information.IMS-Information.Time-Stamps.SIP-Response-Timestamp = "2026-01-24T22:40:18Z"
CCA.Service-Information.IN-Information.Real-Called-Number = "24724741234"
Configuration
Online Charging Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enabled | Boolean | No | false | Master switch for online charging. When false, all calls bypass OCS authorization (no CCRs are sent). See What Controls Whether a Call Is Charged. |
requested_units_seconds | Integer | No | 0 | Talk time (seconds) requested per reservation in the Requested-Service-Unit of CCR-Initial/Update. 0 (default) sends an empty RSU (the AVP is present but carries no CC-Time), letting the OCS choose the grant — this matches the Nokia TAS against the Viti CGRateS, which ignores requested units and computes its own grant. Set a positive value to reserve a specific CC-Time per RFC 4006 for a standards OCS that rates on the request. |
report_and_reserve | Boolean | No | false | 3GPP SCUR usage reporting, matching the Nokia TAS. When true, every CCR-Update and CCR-Terminate carries a Used-Service-Unit reporting the CC-Time consumed since the last report (incremental), plus a 3GPP-Reporting-Reason: RATING_CONDITION_CHANGE (6) on the answer update, QUOTA_EXHAUSTED (3) on periodic updates, FINAL (2) on terminate. The OCS debits each increment and settles the unused tail of the last grant on terminate. When false, only CCR-T reports usage (cumulative from answer, no reason). The sum of incremental CC-Time over a call equals the answered duration. |
service_identifier | Integer | No | 1 | Stable, provisioned Service-Identifier (AVP 439) sent in every CCR. The OCS keys rating on {Service-Context-Id + Service-Identifier}, so this must be a fixed value, not random. |
periodic_ccr_time_seconds | Integer | No | 10 | Fallback interval in seconds between CCR-Update messages when the OCS does not return a usable grant. In normal operation the next CCR-U is timed dynamically from the granted credit (allocated_time - ccr_update_buffer_seconds). |
ccr_update_buffer_seconds | Integer | No | 2 | Safety buffer in seconds before granted credit expires. OmniTAS sends the next CCR-U at (allocated_time - buffer) so credit is extended before it runs out. Recommended: 2-5 seconds. |
schedule_hangup_auth | Boolean | No | false | Enable automatic call hangup/transfer when granted credit expires. When true, OmniTAS schedules a FreeSWITCH timer based on allocated_time from each CCA and reschedules it on every CCR-U response. Works with credit_exhaustion_announcement. |
credit_exhaustion_announcement | String | No | nil | Audio file path for the credit-exhaustion announcement. When configured with schedule_hangup_auth, uses a scheduled transfer to play the announcement before hangup. When configured alone, plays the announcement on immediate credit exhaustion only. Path must use the FreeSWITCH variable form: "${base_dir}/sounds/...". Set to nil for direct hangup without announcement. |
skipped_regex | List[String] | No | [] | List of regex patterns matched against the destination number. Matching destinations bypass OCS entirely (no session, no CCRs). Useful for emergency numbers and service codes (e.g., "^911$", "^000$"). |
Diameter Connection Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
origin_host | String | Yes | - | OmniTAS Diameter Identity (FQDN). Must be unique across your Diameter network. Example: "tas01.epc.mnc123.mcc456.3gppnetwork.org". |
origin_realm | String | Yes | - | OmniTAS Diameter Realm. Used for routing decisions. Example: "epc.mnc123.mcc456.3gppnetwork.org". |
destination_realm | String | Yes | - | OCS Diameter Realm. Requests are routed to peers in this realm. |
destination_host | String | No | nil | Specific OCS Diameter Identity. When nil, routing based on destination_realm only. Use when direct routing to specific OCS instance required. |
Configuration Example
config :tas, :online_charging,
# Master switch
enabled: true,
# 0 = empty RSU, let the OCS decide the grant (matches Nokia TAS / CGRateS).
# Set a positive value only for a standards OCS that rates on the requested units.
requested_units_seconds: 0,
# Stable provisioned service identifier
service_identifier: 1,
# Fallback re-auth interval (normal timing is dynamic from the grant)
periodic_ccr_time_seconds: 10,
# Re-authorize 2s before granted credit expires
ccr_update_buffer_seconds: 2,
# Schedule hangup based on granted credit
schedule_hangup_auth: true,
# Play announcement before credit exhaustion hangup
credit_exhaustion_announcement: "ivr/ivr-account_balance_low.wav",
# Skip OCS for emergency calls and voicemail
skipped_regex: [
"^911$", # Emergency (US)
"^000$", # Emergency (AU)
"^\*86$" # Voicemail access
]
config :tas, :diameter,
# Service identity
origin_host: "tas01.epc.mnc001.mcc001.3gppnetwork.org",
origin_realm: "epc.mnc001.mcc001.3gppnetwork.org",
# OCS routing
destination_realm: "epc.mnc001.mcc001.3gppnetwork.org",
destination_host: nil # Realm-based routing
How it works:
When a call is received:
- Destination number is checked against
skipped_regexpatterns - If matched, call bypasses OCS (useful for emergency services)
- If not matched, CCR-Initial sent to OCS at
destination_realm - CCA response is parsed for granted units and AVPs
- AVPs are mapped to FreeSWITCH variables (see AVP Mapping)
- Call proceeds with
allocated_timeand AVP data available - CCR-Update sent every
periodic_ccr_time_secondsduring call - If
schedule_hangup_authenabled, automatic hangup when credit expires - CCR-Terminate sent on call completion
Use cases:
- Basic OCS: Enable with defaults for standard credit control
- High-value calls: Reduce
periodic_ccr_time_secondsto 30s for frequent re-auth - Prepaid service: Enable
schedule_hangup_authand setcredit_exhaustion_announcement - Emergency compliance: Add emergency numbers to
skipped_regexto ensure always connected
FreeSWITCH Integration
Accessing AVP Variables in Dialplan
AVP data extracted from CCA messages is available as channel variables in FreeSWITCH dialplan:
<extension name="Route_with_OCS_Data">
<condition field="destination_number" expression="^(.+)$">
<!-- Access carrier routing info from OCS -->
<action application="log"
data="INFO Carrier Code: ${CCA.Service-Information.Carrier-Select-Routing-Information}"/>
<!-- Access charged party from OCS -->
<action application="log"
data="INFO Charged Party: ${CCA.Service-Information.Alternate-Charged-Party-Address}"/>
<!-- Access granted time -->
<action application="log"
data="INFO Allocated Time: ${allocated_time} seconds"/>
<!-- Route based on carrier code -->
<action application="set"
data="carrier_code=${CCA.Service-Information.Carrier-Select-Routing-Information}"/>
<action application="bridge"
data="sofia/external/$1@carrier-${carrier_code}.sip.example.com"/>
</condition>
</extension>
Variable Availability
Timing:
- Variables are set before FreeSWITCH call setup
- Available throughout entire call duration
- Persist across call transfers and updates
Scope:
- Channel-scoped (specific to individual call leg)
- Not inherited by bridged/transferred legs
- Safe to use in all dialplan applications
Example Use Cases
1. Carrier Selection Based on OCS Data
Use OCS-provided carrier code to route calls:
<extension name="Carrier_Selection">
<condition field="${CCA.Service-Information.Carrier-Select-Routing-Information}" expression="^(.+)$">
<action application="bridge"
data="sofia/external/${destination_number}@carrier-$1.example.com"/>
</condition>
<!-- Fallback if no carrier specified -->
<condition field="${CCA.Service-Information.Carrier-Select-Routing-Information}" expression="^$">
<action application="bridge"
data="sofia/external/${destination_number}@default-carrier.example.com"/>
</condition>
</extension>
How it works: OCS returns carrier code "1408" in Service-Information AVP. FreeSWITCH routes call to carrier-1408.example.com gateway based on this data.
2. Alternate Billing Party
Route billing to a different party based on OCS response:
<extension name="Alternate_Billing">
<condition field="${CCA.Service-Information.Alternate-Charged-Party-Address}" expression="^(.+)$">
<!-- Log billing party for CDRs -->
<action application="set"
data="billed_party=$1"/>
<action application="export"
data="billed_party=$1"/>
<!-- Include in SIP headers -->
<action application="set"
data="sip_h_X-Billed-Party=$1"/>
<action application="bridge"
data="sofia/external/${destination_number}@trunk.example.com"/>
</condition>
</extension>
How it works: OCS specifies alternate charged party (e.g., corporate account). OmniTAS extracts "NickTest" from AVP and makes it available to dialplan for CDR recording and SIP header insertion.
3. Time-Limited Calls with Warnings
Provide warnings before credit expires:
<extension name="Credit_Warnings">
<condition field="destination_number" expression="^(.+)$">
<!-- Schedule warning 30 seconds before hangup -->
<action application="set"
data="warning_time=${expr(${allocated_time} - 30)}"/>
<action application="sched_hangup"
data="+${allocated_time} ALLOTTED_TIMEOUT"/>
<action application="sched_broadcast"
data="+${warning_time} playback::ivr/ivr-account_balance_low.wav"/>
<action application="bridge"
data="sofia/external/$1@trunk.example.com"/>
</condition>
</extension>
How it works: Uses allocated_time from OCS to schedule automatic hangup and plays warning announcement 30 seconds before disconnection.
Answer & Hangup Notifications
After the CCR-Initial authorizes a call, OmniTAS still needs to know when the call is answered (to begin metering and send the CCR-Update) and when it ends (to send the CCR-Terminate). These events are sourced from FreeSWITCH over the Event Socket Layer (ESL).
How it works:
- OmniTAS holds a persistent inbound ESL connection to the local FreeSWITCH and subscribes to
CHANNEL_ANSWERandCHANNEL_HANGUP_COMPLETE. - On
CHANNEL_ANSWER, it reads the SIP Call-ID and the real answer timestamp and sends the CCR-Update that starts metering. - On
CHANNEL_HANGUP_COMPLETE, it reads the SIP Call-ID and the FreeSWITCH hangup cause and sends the CCR-Terminate with the final used time. - Events are matched to a credit-control session by SIP Call-ID. Events for call legs without a session (MT legs, bridged B-legs, exempt calls) are ignored — this is what keeps non-charged calls from producing CCRs.
- Duplicate answer is idempotent. Both legs of a bridged call fire
CHANNEL_ANSWERand can resolve to the same Call-ID; only the first sends a CCR-Update and starts the periodic sender, the rest are ignored (otherwise you get duplicate CCR-Us and double periodic senders). - A transfer ends the charged session. A
BLIND_TRANSFER/ATTENDED_TRANSFERhangup is treated as a normal hangup → CCR-Terminate (the charged caller has left the call, or the call was transferred to the out-of-credit announcement). Once a session has terminated, any later hangup for the same Call-ID finds no session and sends nothing.
The CallEventListener always runs (a real deployment always has the co-located
FreeSWITCH; it is skipped only under test_mode). It is not gated by a separate switch —
whether a CCR is sent is decided per event by online_charging.enabled. With charging off
the listener still receives and logs CHANNEL_ANSWER/CHANNEL_HANGUP_COMPLETE at debug but
sends no CCR. (Earlier releases had a separate esl_notifications toggle; it was removed
because it had to agree with online_charging.enabled and silently broke charging — only
CCR-Initial, no CCR-U/CCR-T — when charging was on but the toggle was absent.)
Deprecated: /call_event HTTP endpoint
Earlier releases detected answer/hangup via FreeSWITCH dialplan curl hooks that POSTed to a
/call_event HTTP endpoint on OmniTAS. This mechanism is deprecated. The dialplan hooks
have been removed and ESL is now the notification source.
The /call_event endpoint still exists and returns 200 OK so that any lingering dialplan
references do not error, but it performs no charging action. Operators upgrading from an
older release should ensure their dialplans no longer rely on it.
Diameter Messages
CCR-Initial (Request Type 1)
Sent before call setup to request authorization and initial credit allocation.
Key AVPs Sent:
| AVP | Code | Type | Description |
|---|---|---|---|
| Session-Id | 263 | UTF8String | Session identifier, constant for the whole credit-control session (CCR-I/U/T share it). Derived deterministically from the SIP Call-ID. |
| Auth-Application-Id | 258 | Unsigned32 | Value 4 for Diameter Credit Control Application per RFC 4006 |
| Service-Context-Id | 461 | UTF8String | "000.000.12.32260@3gpp.org" for IMS charging per TS 32.299 |
| CC-Request-Type | 416 | Enumerated | Value 1 (INITIAL_REQUEST) |
| CC-Request-Number | 415 | Unsigned32 | Sequence number — 0 for INITIAL_REQUEST, then 1, 2, … for subsequent requests in the session per RFC 4006 §8.2 |
| Service-Identifier | 439 | Unsigned32 | Stable provisioned service identifier (service_identifier config) |
| Subscription-Id | 443 | Grouped | Subscriber MSISDN or IMSI |
| Requested-Service-Unit | 437 | Grouped | Reservation request — CC-Time = requested_units_seconds |
| Service-Information | 873 | Grouped | IMS-specific call details (calling/called party, Role-Of-Node, timestamps) |
Example CCR-I:
Session-Id: "tas01.example.org;1463927445;1744753804"
Auth-Application-Id: 4
CC-Request-Type: 1 (INITIAL_REQUEST)
CC-Request-Number: 0
Subscription-Id:
- Subscription-ID-Type: 0 (END_USER_E164)
Subscription-ID-Data: "313380000000670"
Multiple-Services-Credit-Control:
- Service-Identifier: 1
Requested-Service-Unit:
- CC-Time: 300 (requested reservation)
Service-Information:
- IMS-Information:
- Role-Of-Node: 0 (ORIGINATING_ROLE)
- Node-Functionality: 6 (AS)
- Calling-Party-Address: "tel:+313380000000670"
- Called-Party-Address: "tel:+24724741234"
The
Requested-Service-Unitcarries a real reservation (not zero). The OCS decides the actual grant and returns it in the CCA'sGranted-Service-Unit.
CCA (Credit-Control-Answer)
Response from OCS with authorization decision and granted credit.
Key AVPs Received:
| AVP | Code | Type | Description |
|---|---|---|---|
| Result-Code | 268 | Unsigned32 | 2001 for success. See Result Codes for error values. |
| Granted-Service-Unit | 431 | Grouped | Allocated credit (time in seconds) |
| Service-Information | 873 | Grouped | Additional charging data (carrier info, charged party, etc.) |
Example CCA with AVPs:
Session-Id: "tas01.example.org;1769294418268;8a078232"
Result-Code: 2001 (DIAMETER_SUCCESS)
CC-Request-Type: 1
CC-Request-Number: 1
Granted-Service-Unit:
- CC-Time: 600 (10 minutes granted)
Service-Information:
- IMS-Information:
- Carrier-Select-Routing-Information: "1408"
- Alternate-Charged-Party-Address: "NickTest"
Resulting Variables:
allocated_time = 600
CCA.Service-Information.Carrier-Select-Routing-Information = "1408"
CCA.Service-Information.Alternate-Charged-Party-Address = "NickTest"
CCR-Update (Request Type 2)
Sent during active calls for periodic re-authorization or interim usage reporting.
When Sent:
- On call answer (
CHANNEL_ANSWERfrom ESL) —CC-Request-Number=1, with the real answer time in theSIP-Response-Timestamp - Periodically during the active call, timed dynamically as
allocated_time - ccr_update_buffer_secondsfrom each grant (falling back toperiodic_ccr_time_seconds)
Key Differences from CCR-I:
CC-Request-Type:2(UPDATE_REQUEST)CC-Request-Number:1on answer, then increments on each periodic re-authRequested-Service-Unit: the next reservation (requested_units_seconds)- Same constant
Session-Idas the CCR-Initial
Consumed time is reported in the CCR-Terminate (
Used-Service-Unit), not in interim CCR-Updates. Interim updates extend the reservation; final usage is settled at termination.
CCR-Terminate (Request Type 3)
Sent on call hangup (CHANNEL_HANGUP_COMPLETE from ESL) with final usage reporting.
Key AVPs:
CC-Request-Type:3(TERMINATION_REQUEST)CC-Request-Number: next in sequence (e.g.2)Used-Service-Unit: total consumed talk time (CC-Time, seconds since answer)Termination-Cause:1(DIAMETER_LOGOUT) per RFC 6733 §8.15Cause-Code(AVP 861, IMS-Information):0for a normally answered-and-released call,2for an unsuccessful session setup, per TS 32.299
Result Codes
| Code | Name | Description | OmniTAS Action |
|---|---|---|---|
| 2001 | DIAMETER_SUCCESS | Request approved | Parse AVPs, setup call |
| 4010 | DIAMETER_END_USER_SERVICE_DENIED | Service denied for subscriber | Reject call with CALL_REJECTED |
| 4012 | DIAMETER_CREDIT_LIMIT_REACHED | Insufficient credit | Reject call with OUTGOING_CALL_BARRED |
| 5003 | DIAMETER_AUTHORIZATION_REJECTED | OCS policy denied | Reject call with CALL_REJECTED |
| 5xxx | Permanent failures | OCS configuration or system error | Reject call, log error |
Reference: RFC 6733 §7.1 and 3GPP TS 32.299
Metrics
See doc/metrics.md for the full metric catalogue. The metrics below are the ones relevant to online charging.
Diameter Request / Response Metrics
CCRs are split by request type via the command label so CCR-Initial (setup),
CCR-Update (interim re-auth) and CCR-Terminate (teardown) can be tracked
independently — a spike in failed interim updates is then visible on its own.
Metric: diameter_requests_total (Counter) — Diameter requests sent
Metric: diameter_responses_total (Counter) — Diameter responses received
Metric: diameter_response_duration_milliseconds (Histogram) — request round-trip time
Labels:
application-ro(online charging) orsh(subscriber data)command-ccr_i,ccr_u,ccr_t(Ro) orudr(Sh)result_code- (responses only) Diameter Result-Code:2001,4012, etc.;0= timeout / unparseable replyresult- (duration only)success,nocredit,error
Example queries:
# CCR rate by request type
sum by (command) (rate(diameter_requests_total{application="ro"}[5m]))
# CCR error rate, per request type (anything that is not 2001)
sum by (command) (rate(diameter_responses_total{application="ro", result_code!="2001"}[5m]))
# Credit limit rejections (4012)
rate(diameter_responses_total{application="ro", result_code="4012"}[5m])
# 95th percentile CCR latency by request type
histogram_quantile(0.95,
sum by (le, command) (rate(diameter_response_duration_milliseconds_bucket{application="ro"}[5m]))
)
Credit-Control Quota Metrics
Metric: ro_charging_quota_seconds
Type: Histogram
Description: Quota (in seconds) observed on each CCR, for verifying charging correctness
Labels:
request_type-ccr_i,ccr_u,ccr_tkind-requested(CC-Time in Requested-Service-Unit),granted(Granted-Service-Unit,0= no credit),used(Used-Service-Unit on terminate)
Example queries:
# Median granted quota at call setup
histogram_quantile(0.5, rate(ro_charging_quota_seconds_bucket{request_type="ccr_i", kind="granted"}[5m]))
# No-credit grants (zero-second allocations) per second
rate(ro_charging_quota_seconds_bucket{kind="granted", le="0"}[5m])
# Total used seconds reported on termination per second (sanity-check against billed minutes)
rate(ro_charging_quota_seconds_sum{kind="used"}[5m])
OCS Authorization & Event Metrics
Metric: ocs_authorization_attempts_total (Counter)
Labels: result (success, nocredit, timeout, error), skipped (true if bypassed via regex, else false)
Metric: online_charging_events_total (Counter) — lifecycle events
Labels: event_type (authorize, answer, reauth, hangup, credit_exhaustion_hangup, hangup_rescheduled), result (success, nocredit, timeout, error, triggered)
Example queries:
# Authorization success rate (excluding skipped)
sum(rate(ocs_authorization_attempts_total{result="success", skipped="false"}[5m]))
/ sum(rate(ocs_authorization_attempts_total{skipped="false"}[5m]))
# Calls released mid-call due to credit exhaustion
rate(online_charging_events_total{event_type="credit_exhaustion_hangup"}[5m])
Troubleshooting
AVP Variables Not Available in FreeSWITCH
Symptoms:
- FreeSWITCH dialplan cannot access
${CCA.Service-Information.*}variables - Variables show as empty or undefined
Possible causes:
- OCS not returning Service-Information AVPs in CCA
- AVP parsing failed due to unexpected structure
- Variables not exported to FreeSWITCH channel
Resolution:
-
Verify OCS Response Contains AVPs
Check OmniTAS logs for CCA message:
[debug] Credit Control Answer: {:diameter_packet, ...}
[debug] Parsed AVP variables: %{
"CCA.Service-Information.Carrier-Select-Routing-Information" => "1408",
"CCA.Service-Information.Alternate-Charged-Party-Address" => "NickTest"
}If "Parsed AVP variables" is empty
%{}, OCS is not returning the expected AVPs. -
Check for AVP Parsing Errors
Look for warnings in logs:
[warning] got back another type of reply: {...}This indicates AVP structure doesn't match expected format. Check Diameter packet structure.
-
Verify FreeSWITCH Variable Export
In FreeSWITCH console or ESL:
freeswitch> uuid_dump <call-uuid>Look for variables with the
variable_prefix andCCA.in the name:variable_CCA.Service-Information.Carrier-Select-Routing-Information: 1408
variable_CCA.Service-Information.Alternate-Charged-Party-Address: NickTest
variable_CCA.Auth-Application-Id: 4
variable_CCA.Result-Code: 2001Note: FreeSWITCH preserves dots and hyphens in variable names. They work correctly in dialplan:
<action application="log" data="Carrier: ${CCA.Service-Information.Carrier-Select-Routing-Information}"/>
Call Rejected with "unhandled" Error
Symptoms:
- Logs show:
[warning] Could not authorize call: :unhandled - Valid CCA responses (Result-Code 2001) are rejected
- Calls fail despite OCS approving them
Possible causes:
- CCA message structure doesn't match expected pattern
- Vendor-specific AVPs in unexpected positions
- AVP position index mismatch
Resolution:
This was a known issue fixed in recent releases. Ensure you're running current version.
Previous behavior: Pattern matching required:
- Granted-Service-Unit AVP at position 7 exactly
- Empty vendor-specific AVP list
[]
Current behavior: Pattern matching accepts:
- Granted-Service-Unit AVP at any position
- Non-empty vendor-specific AVP lists
If issue persists:
- Capture CCA packet structure from logs
- Check if AVPs are in expected Diameter format
- Verify Result-Code is 2001
OCS Timeout on All Requests
Symptoms:
- All CCR requests timeout
- Logs show:
[debug] Got back response for authorize: {:error, :timeout} - No CCA received within 5 seconds
Possible causes:
- Network connectivity to OCS/DRA
- Firewall blocking Diameter port (3868)
- Incorrect
destination_realmordestination_host - OCS not responding to requests
Resolution:
-
Verify Network Connectivity
Test TCP connection to OCS:
telnet ocs.example.com 3868Should connect successfully. If connection refused or timeout, check firewall rules.
-
Check Diameter Configuration
Verify
destination_realmmatches OCS configuration:config :tas, :diameter,
destination_realm: "epc.mnc001.mcc001.3gppnetwork.org" # Must match OCS realm -
Review OCS Logs
Check OCS for incoming CCR messages. If OCS receives requests but doesn't respond:
- Verify OmniTAS
origin_hostis recognized by OCS - Check OCS peer configuration allows connections from OmniTAS
- Verify Service-Context-Id and Application-Id match OCS expectations
- Verify OmniTAS
Credit Exhaustion Not Hanging Up Calls
Symptoms:
- Calls continue beyond granted credit time
- No automatic hangup when
allocated_timeexpires schedule_hangup_authenabled but not working
Possible causes:
- FreeSWITCH scheduled hangup not configured
schedule_hangup_authisfalse- Call state not tracked properly
Resolution:
-
Verify Configuration
Ensure
schedule_hangup_authis enabled:config :tas, :online_charging,
schedule_hangup_auth: true -
Check FreeSWITCH ESL Connection
Verify OmniTAS can send commands to FreeSWITCH:
[debug] Schedule Hangup Response: {:ok, "+OK"}If error or no response, check FreeSWITCH Event Socket configuration.
-
Monitor Call State
Check that call UUID is tracked in call state:
[debug] Setting Scheduled Hangup for call in 600 secondsIf UUID not found, call state tracking may have issues.
Skipped Regex Not Bypassing OCS
Symptoms:
- Emergency calls (911, 000) still go through OCS authorization
- Numbers matching
skipped_regexpatterns are not bypassed - Delays on emergency calls
Possible causes:
- Regex pattern syntax error
- Destination number format mismatch
- Regex not properly escaped
Resolution:
-
Verify Regex Patterns
Test regex compilation:
Regex.compile("^911$") # Should return {:ok, ~r/^911$/}Common mistakes:
- Missing anchors: Use
^911$not911 - Escaping: Use
\*for literal asterisk, not\*
- Missing anchors: Use
-
Check Number Format
Verify destination number format matches pattern:
[debug] Checking if dialled number "911" matches skipped regex...If number is formatted as "+1911" but pattern is "^911$", it won't match.
-
Example Patterns
config :tas, :online_charging,
skipped_regex: [
"^911$", # US Emergency
"^000$", # AU Emergency
"^112$", # International Emergency
"^\*86$", # Voicemail (escaped asterisk)
"^1?800\d{7}$" # Toll-free numbers
]
Reference
3GPP Specifications
| Specification | Title | Relevant Sections |
|---|---|---|
| TS 32.299 | Diameter charging applications | §6.3 (Ro interface), §7.2 (AVP definitions) |
| TS 32.240 | Charging architecture and principles | §5 (Online charging) |
| TS 29.229 | Cx and Dx interfaces | Service-Information AVP usage in IMS |
IETF RFCs
| RFC | Title | Relevant Sections |
|---|---|---|
| RFC 6733 | Diameter Base Protocol | §3 (Protocol overview), §7 (Error handling) |
| RFC 4006 | Diameter Credit-Control Application | §8 (Credit-Control messages) |
AVP Codes Reference
Vendor-ID 0 = IETF base (RFC 6733 /
RFC 4006); Vendor-ID 10415 = 3GPP
(TS 32.299).
Complete CCR structure (voice call)
Every AVP OmniTAS includes in a call Credit-Control-Request, with grouping shown:
Credit-Control-Request (Command 272, App 4)
├─ Session-Id (263)
├─ Origin-Host (264)
├─ Origin-Realm (296)
├─ Destination-Realm (283)
├─ Destination-Host (293) [only if configured]
├─ Auth-Application-Id (258) = 4
├─ Service-Context-Id (461) = 000.000.12.32260@3gpp.org
├─ CC-Request-Type (416)
├─ CC-Request-Number (415)
├─ Event-Timestamp (55)
├─ User-Name (1) [only if username set]
├─ Termination-Cause (295) [CCR-T only]
├─ Subscription-Id (443)
│ ├─ Subscription-Id-Type (450) = 0 (END_USER_E164)
│ └─ Subscription-Id-Data (444) = subscriber MSISDN
├─ Multiple-Services-Credit-Control (456)
│ ├─ Service-Identifier (439)
│ ├─ Requested-Service-Unit (437) [CCR-I, CCR-U]
│ │ └─ CC-Time (420)
│ └─ Used-Service-Unit (446) [CCR-T]
│ └─ CC-Time (420)
└─ Service-Information (873, v10415)
├─ IN-Information [operator extension]
│ └─ Real-Called-Number
└─ IMS-Information (876, v10415)
├─ Role-Of-Node (829) = 0 (ORIGINATING_ROLE)
├─ Node-Functionality (862) = 6 (AS)
├─ User-Session-Id (830) = SIP Call-ID
├─ Calling-Party-Address (831)
├─ Called-Party-Address (832)
├─ Requested-Party-Address (1251)
├─ Time-Stamps (833)
│ ├─ SIP-Request-Timestamp (834)
│ ├─ SIP-Request-Timestamp-Fraction (2301)
│ ├─ SIP-Response-Timestamp (835) [once answered]
│ └─ SIP-Response-Timestamp-Fraction (2302) [once answered]
└─ Cause-Code (861) [CCR-T]
AVPs sent by OmniTAS (CCR)
The In column shows which request types carry the AVP: I = CCR-Initial, U = CCR-Update, T = CCR-Terminate.
| AVP | Code | Vendor | Type | In | Value / Source |
|---|---|---|---|---|---|
| Session-Id | 263 | 0 | UTF8String | I U T | Constant per call (derived from SIP Call-ID); identical across I/U/T |
| Origin-Host | 264 | 0 | DiameterIdentity | I U T | OmniTAS Diameter identity (origin_host.origin_realm) |
| Origin-Realm | 296 | 0 | DiameterIdentity | I U T | origin_realm |
| Destination-Realm | 283 | 0 | DiameterIdentity | I U T | OCS realm |
| Destination-Host | 293 | 0 | DiameterIdentity | I U T | Only present if a specific OCS host is configured |
| Auth-Application-Id | 258 | 0 | Unsigned32 | I U T | 4 (Diameter Credit Control Application) |
| Service-Context-Id | 461 | 0 | UTF8String | I U T | 000.000.12.32260@3gpp.org |
| CC-Request-Type | 416 | 0 | Enumerated | I U T | 1=Initial, 2=Update, 3=Terminate |
| CC-Request-Number | 415 | 0 | Unsigned32 | I U T | 0 for Initial, then 1, 2, … |
| Event-Timestamp | 55 | 0 | Time | I U T | Time the request was generated |
| User-Name | 1 | 0 | UTF8String | I U T | Optional; only if a username/IMSI is supplied |
| Termination-Cause | 295 | 0 | Enumerated | T | 1 (DIAMETER_LOGOUT) |
| Subscription-Id | 443 | 0 | Grouped | I U T | Subscriber identity (see children) |
| → Subscription-Id-Type | 450 | 0 | Enumerated | I U T | 0 (END_USER_E164) |
| → Subscription-Id-Data | 444 | 0 | UTF8String | I U T | Subscriber MSISDN |
| Multiple-Services-Credit-Control | 456 | 0 | Grouped | I U T | Credit-control container (see children) |
| → Service-Identifier | 439 | 0 | Unsigned32 | I U T | service_identifier (stable, provisioned) |
| → Requested-Service-Unit | 437 | 0 | Grouped | I U | Reservation request |
| → → CC-Time | 420 | 0 | Unsigned32 | I U | requested_units_seconds |
| → Used-Service-Unit | 446 | 0 | Grouped | T | Final consumption |
| → → CC-Time | 420 | 0 | Unsigned32 | T | Consumed seconds since answer |
| Service-Information | 873 | 10415 | Grouped | I U T | 3GPP service container |
| → IN-Information | — | 10415 | Grouped | I U T | Operator extension carrying the real dialled number |
| → → Real-Called-Number | — | 10415 | UTF8String | I U T | Dialled (called) number |
| → IMS-Information | 876 | 10415 | Grouped | I U T | IMS charging details |
| → → Role-Of-Node | 829 | 10415 | Enumerated | I U T | 0 (ORIGINATING_ROLE) — MO is the charged leg |
| → → Node-Functionality | 862 | 10415 | Enumerated | I U T | 6 (AS) |
| → → User-Session-Id | 830 | 10415 | UTF8String | I U T | SIP Call-ID (OCS correlation key) |
| → → Calling-Party-Address | 831 | 10415 | UTF8String | I U T | tel:+<calling MSISDN> |
| → → Called-Party-Address | 832 | 10415 | UTF8String | I U T | tel:+<called number> |
| → → Requested-Party-Address | 1251 | 10415 | UTF8String | I U T | tel:+<called number> |
| → → Time-Stamps | 833 | 10415 | Grouped | I U T | SIP request/response times |
| → → → SIP-Request-Timestamp | 834 | 10415 | Time | I U T | INVITE time (whole seconds) |
| → → → SIP-Request-Timestamp-Fraction | 2301 | 10415 | Unsigned32 | I U T | INVITE time (milliseconds) |
| → → → SIP-Response-Timestamp | 835 | 10415 | Time | U T | Answer (200 OK) time — only once answered |
| → → → SIP-Response-Timestamp-Fraction | 2302 | 10415 | Unsigned32 | U T | Answer time (milliseconds) — only once answered |
| → → Cause-Code | 861 | 10415 | Integer32 | T | 0 normal release / 2 unsuccessful setup |
Codes shown as
—are operator/vendor extensions carried underService-Information; they have no 3GPP-assigned code and are defined in the OmniTAS Diameter dictionary.
AVPs received from the OCS (CCA)
| AVP | Code | Vendor | Type | Description |
|---|---|---|---|---|
| Result-Code | 268 | 0 | Unsigned32 | 2001 = success; see Result Codes |
| Granted-Service-Unit | 431 | 0 | Grouped | Allocated credit |
| → CC-Time | 420 | 0 | Unsigned32 | Granted seconds (allocated_time). 0 ⇒ no credit ⇒ bar/hangup |
| Service-Information | 873 | 10415 | Grouped | Optional charging data returned by the OCS |
| → Carrier-Select-Routing-Information | 2023 | 10415 | UTF8String | Carrier routing code (mapped to a FreeSWITCH variable) |
| → Alternate-Charged-Party-Address | 1280 | 10415 | UTF8String | Billing party identifier (mapped to a FreeSWITCH variable) |
All Service-Information sub-AVPs returned in the CCA are flattened into FreeSWITCH channel
variables — see AVP Parsing and Variable Mapping and
FreeSWITCH Channel Variables.
FreeSWITCH Channel Variables
All extracted AVP data is available as FreeSWITCH channel variables:
| Variable Name | Source | Example Value | Description |
|---|---|---|---|
${allocated_time} | Granted-Service-Unit / CC-Time | 600 | Allocated time in seconds |
${CCA.Session-Id} | Session-Id AVP | omni-as01.epc...;1769299669873;325e2f2e | Diameter session identifier |
${CCA.Result-Code} | Result-Code AVP | 2001 | CCA result (2001 = success) |
${CCA.Auth-Application-Id} | Auth-Application-Id AVP | 4 | Diameter application (4 = CC) |
${CCA.CC-Request-Type} | CC-Request-Type AVP | 1 | Request type (1=Initial) |
${CCA.CC-Request-Number} | CC-Request-Number AVP | 1 | Sequence number |
${CCA.CC-Time} | CC-Time AVP (if present) | 600 | Granted time quota |
${CCA.Origin-Host} | Origin-Host AVP | ocs01.epc.mnc001.mcc001.3gppnetwork.org | OCS host identifier |
${CCA.Origin-Realm} | Origin-Realm AVP | epc.mnc001.mcc001.3gppnetwork.org | OCS realm |
${CCA.Service-Information.Carrier-Select-Routing-Information} | Service-Information → Carrier-Select-Routing-Information | 1408 | Carrier routing code from OCS |
${CCA.Service-Information.Alternate-Charged-Party-Address} | Service-Information → Alternate-Charged-Party-Address | NickTest | Alternate billing party |
Variable Format:
- All CCA AVPs use the prefix
CCA. - Nested AVPs use dot notation:
CCA.Parent.Child - Dots and hyphens are preserved in variable names
- In uuid_dump, variables appear with
variable_prefix
Example uuid_dump output:
variable_allocated_time: 600
variable_CCA.Service-Information.Carrier-Select-Routing-Information: 1408
variable_CCA.Service-Information.Alternate-Charged-Party-Address: NickTest
variable_CCA.Result-Code: 2001