Skip to main content

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

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:

  1. Online charging is enabledonline_charging.enabled is true. When false, OmniTAS authorizes every call locally and never contacts the OCS.
  2. 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.
  3. 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:

  1. OmniTAS sets tas_call_reason=credit_exhausted channel variable
  2. Schedules transfer to credit_exhausted extension in ims_as dialplan context
  3. 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 ResponseActionLogs
{:ok, 0} (Zero seconds)Immediate credit exhaustion hangupCredit exhausted (zero seconds allocated) - triggering immediate hangup
{:error, 4012} (CREDIT_LIMIT_REACHED)Immediate credit exhaustion hangupCredit exhausted (4012 CREDIT_LIMIT_REACHED) - triggering immediate hangup
{:error, 4010} (END_USER_SERVICE_DENIED)Immediate credit exhaustion hangupService denied (4010 END_USER_SERVICE_DENIED) - triggering immediate hangup
{:error, reason} (Other errors)Stop periodic CCR job, scheduled timer firesPeriodic CCR failed with error <reason> - Stopping job
{:ok, N} where N > 0Reschedule timer to +N secondsPeriodic 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:

  1. 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
  2. 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:

  1. Grouped AVPs add a level to the variable name hierarchy but have no value themselves
  2. Simple AVPs are mapped to variables with their full dotted path
  3. Vendor-Specific AVPs are processed identically to standard AVPs
  4. 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

ParameterTypeRequiredDefaultDescription
enabledBooleanNofalseMaster 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_secondsIntegerNo0Talk 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_reserveBooleanNofalse3GPP 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_identifierIntegerNo1Stable, 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_secondsIntegerNo10Fallback 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_secondsIntegerNo2Safety 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_authBooleanNofalseEnable 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_announcementStringNonilAudio 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_regexList[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

ParameterTypeRequiredDefaultDescription
origin_hostStringYes-OmniTAS Diameter Identity (FQDN). Must be unique across your Diameter network. Example: "tas01.epc.mnc123.mcc456.3gppnetwork.org".
origin_realmStringYes-OmniTAS Diameter Realm. Used for routing decisions. Example: "epc.mnc123.mcc456.3gppnetwork.org".
destination_realmStringYes-OCS Diameter Realm. Requests are routed to peers in this realm.
destination_hostStringNonilSpecific 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:

  1. Destination number is checked against skipped_regex patterns
  2. If matched, call bypasses OCS (useful for emergency services)
  3. If not matched, CCR-Initial sent to OCS at destination_realm
  4. CCA response is parsed for granted units and AVPs
  5. AVPs are mapped to FreeSWITCH variables (see AVP Mapping)
  6. Call proceeds with allocated_time and AVP data available
  7. CCR-Update sent every periodic_ccr_time_seconds during call
  8. If schedule_hangup_auth enabled, automatic hangup when credit expires
  9. CCR-Terminate sent on call completion

Use cases:

  • Basic OCS: Enable with defaults for standard credit control
  • High-value calls: Reduce periodic_ccr_time_seconds to 30s for frequent re-auth
  • Prepaid service: Enable schedule_hangup_auth and set credit_exhaustion_announcement
  • Emergency compliance: Add emergency numbers to skipped_regex to 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_ANSWER and CHANNEL_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_ANSWER and 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_TRANSFER hangup 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:

AVPCodeTypeDescription
Session-Id263UTF8StringSession identifier, constant for the whole credit-control session (CCR-I/U/T share it). Derived deterministically from the SIP Call-ID.
Auth-Application-Id258Unsigned32Value 4 for Diameter Credit Control Application per RFC 4006
Service-Context-Id461UTF8String"000.000.12.32260@3gpp.org" for IMS charging per TS 32.299
CC-Request-Type416EnumeratedValue 1 (INITIAL_REQUEST)
CC-Request-Number415Unsigned32Sequence number — 0 for INITIAL_REQUEST, then 1, 2, … for subsequent requests in the session per RFC 4006 §8.2
Service-Identifier439Unsigned32Stable provisioned service identifier (service_identifier config)
Subscription-Id443GroupedSubscriber MSISDN or IMSI
Requested-Service-Unit437GroupedReservation request — CC-Time = requested_units_seconds
Service-Information873GroupedIMS-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-Unit carries a real reservation (not zero). The OCS decides the actual grant and returns it in the CCA's Granted-Service-Unit.

CCA (Credit-Control-Answer)

Response from OCS with authorization decision and granted credit.

Key AVPs Received:

AVPCodeTypeDescription
Result-Code268Unsigned322001 for success. See Result Codes for error values.
Granted-Service-Unit431GroupedAllocated credit (time in seconds)
Service-Information873GroupedAdditional 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_ANSWER from ESL) — CC-Request-Number = 1, with the real answer time in the SIP-Response-Timestamp
  • Periodically during the active call, timed dynamically as allocated_time - ccr_update_buffer_seconds from each grant (falling back to periodic_ccr_time_seconds)

Key Differences from CCR-I:

  • CC-Request-Type: 2 (UPDATE_REQUEST)
  • CC-Request-Number: 1 on answer, then increments on each periodic re-auth
  • Requested-Service-Unit: the next reservation (requested_units_seconds)
  • Same constant Session-Id as 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.15
  • Cause-Code (AVP 861, IMS-Information): 0 for a normally answered-and-released call, 2 for an unsuccessful session setup, per TS 32.299

Result Codes

CodeNameDescriptionOmniTAS Action
2001DIAMETER_SUCCESSRequest approvedParse AVPs, setup call
4010DIAMETER_END_USER_SERVICE_DENIEDService denied for subscriberReject call with CALL_REJECTED
4012DIAMETER_CREDIT_LIMIT_REACHEDInsufficient creditReject call with OUTGOING_CALL_BARRED
5003DIAMETER_AUTHORIZATION_REJECTEDOCS policy deniedReject call with CALL_REJECTED
5xxxPermanent failuresOCS configuration or system errorReject 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) or sh (subscriber data)
  • command - ccr_i, ccr_u, ccr_t (Ro) or udr (Sh)
  • result_code - (responses only) Diameter Result-Code: 2001, 4012, etc.; 0 = timeout / unparseable reply
  • result - (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_t
  • kind - 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:

  1. OCS not returning Service-Information AVPs in CCA
  2. AVP parsing failed due to unexpected structure
  3. Variables not exported to FreeSWITCH channel

Resolution:

  1. 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.

  2. 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.

  3. Verify FreeSWITCH Variable Export

    In FreeSWITCH console or ESL:

    freeswitch> uuid_dump <call-uuid>

    Look for variables with the variable_ prefix and CCA. 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: 2001

    Note: 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:

  1. Capture CCA packet structure from logs
  2. Check if AVPs are in expected Diameter format
  3. 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_realm or destination_host
  • OCS not responding to requests

Resolution:

  1. Verify Network Connectivity

    Test TCP connection to OCS:

    telnet ocs.example.com 3868

    Should connect successfully. If connection refused or timeout, check firewall rules.

  2. Check Diameter Configuration

    Verify destination_realm matches OCS configuration:

    config :tas, :diameter,
    destination_realm: "epc.mnc001.mcc001.3gppnetwork.org" # Must match OCS realm
  3. Review OCS Logs

    Check OCS for incoming CCR messages. If OCS receives requests but doesn't respond:

    • Verify OmniTAS origin_host is recognized by OCS
    • Check OCS peer configuration allows connections from OmniTAS
    • Verify Service-Context-Id and Application-Id match OCS expectations

Credit Exhaustion Not Hanging Up Calls

Symptoms:

  • Calls continue beyond granted credit time
  • No automatic hangup when allocated_time expires
  • schedule_hangup_auth enabled but not working

Possible causes:

  • FreeSWITCH scheduled hangup not configured
  • schedule_hangup_auth is false
  • Call state not tracked properly

Resolution:

  1. Verify Configuration

    Ensure schedule_hangup_auth is enabled:

    config :tas, :online_charging,
    schedule_hangup_auth: true
  2. 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.

  3. Monitor Call State

    Check that call UUID is tracked in call state:

    [debug] Setting Scheduled Hangup for call in 600 seconds

    If 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_regex patterns are not bypassed
  • Delays on emergency calls

Possible causes:

  • Regex pattern syntax error
  • Destination number format mismatch
  • Regex not properly escaped

Resolution:

  1. Verify Regex Patterns

    Test regex compilation:

    Regex.compile("^911$")  # Should return {:ok, ~r/^911$/}

    Common mistakes:

    • Missing anchors: Use ^911$ not 911
    • Escaping: Use \* for literal asterisk, not \*
  2. 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.

  3. 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

SpecificationTitleRelevant Sections
TS 32.299Diameter charging applications§6.3 (Ro interface), §7.2 (AVP definitions)
TS 32.240Charging architecture and principles§5 (Online charging)
TS 29.229Cx and Dx interfacesService-Information AVP usage in IMS

IETF RFCs

RFCTitleRelevant Sections
RFC 6733Diameter Base Protocol§3 (Protocol overview), §7 (Error handling)
RFC 4006Diameter 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.

AVPCodeVendorTypeInValue / Source
Session-Id2630UTF8StringI U TConstant per call (derived from SIP Call-ID); identical across I/U/T
Origin-Host2640DiameterIdentityI U TOmniTAS Diameter identity (origin_host.origin_realm)
Origin-Realm2960DiameterIdentityI U Torigin_realm
Destination-Realm2830DiameterIdentityI U TOCS realm
Destination-Host2930DiameterIdentityI U TOnly present if a specific OCS host is configured
Auth-Application-Id2580Unsigned32I U T4 (Diameter Credit Control Application)
Service-Context-Id4610UTF8StringI U T000.000.12.32260@3gpp.org
CC-Request-Type4160EnumeratedI U T1=Initial, 2=Update, 3=Terminate
CC-Request-Number4150Unsigned32I U T0 for Initial, then 1, 2, …
Event-Timestamp550TimeI U TTime the request was generated
User-Name10UTF8StringI U TOptional; only if a username/IMSI is supplied
Termination-Cause2950EnumeratedT1 (DIAMETER_LOGOUT)
Subscription-Id4430GroupedI U TSubscriber identity (see children)
→ Subscription-Id-Type4500EnumeratedI U T0 (END_USER_E164)
→ Subscription-Id-Data4440UTF8StringI U TSubscriber MSISDN
Multiple-Services-Credit-Control4560GroupedI U TCredit-control container (see children)
→ Service-Identifier4390Unsigned32I U Tservice_identifier (stable, provisioned)
→ Requested-Service-Unit4370GroupedI UReservation request
→ → CC-Time4200Unsigned32I Urequested_units_seconds
→ Used-Service-Unit4460GroupedTFinal consumption
→ → CC-Time4200Unsigned32TConsumed seconds since answer
Service-Information87310415GroupedI U T3GPP service container
→ IN-Information10415GroupedI U TOperator extension carrying the real dialled number
→ → Real-Called-Number10415UTF8StringI U TDialled (called) number
→ IMS-Information87610415GroupedI U TIMS charging details
→ → Role-Of-Node82910415EnumeratedI U T0 (ORIGINATING_ROLE) — MO is the charged leg
→ → Node-Functionality86210415EnumeratedI U T6 (AS)
→ → User-Session-Id83010415UTF8StringI U TSIP Call-ID (OCS correlation key)
→ → Calling-Party-Address83110415UTF8StringI U Ttel:+<calling MSISDN>
→ → Called-Party-Address83210415UTF8StringI U Ttel:+<called number>
→ → Requested-Party-Address125110415UTF8StringI U Ttel:+<called number>
→ → Time-Stamps83310415GroupedI U TSIP request/response times
→ → → SIP-Request-Timestamp83410415TimeI U TINVITE time (whole seconds)
→ → → SIP-Request-Timestamp-Fraction230110415Unsigned32I U TINVITE time (milliseconds)
→ → → SIP-Response-Timestamp83510415TimeU TAnswer (200 OK) time — only once answered
→ → → SIP-Response-Timestamp-Fraction230210415Unsigned32U TAnswer time (milliseconds) — only once answered
→ → Cause-Code86110415Integer32T0 normal release / 2 unsuccessful setup

Codes shown as are operator/vendor extensions carried under Service-Information; they have no 3GPP-assigned code and are defined in the OmniTAS Diameter dictionary.

AVPs received from the OCS (CCA)

AVPCodeVendorTypeDescription
Result-Code2680Unsigned322001 = success; see Result Codes
Granted-Service-Unit4310GroupedAllocated credit
→ CC-Time4200Unsigned32Granted seconds (allocated_time). 0 ⇒ no credit ⇒ bar/hangup
Service-Information87310415GroupedOptional charging data returned by the OCS
→ Carrier-Select-Routing-Information202310415UTF8StringCarrier routing code (mapped to a FreeSWITCH variable)
→ Alternate-Charged-Party-Address128010415UTF8StringBilling 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 NameSourceExample ValueDescription
${allocated_time}Granted-Service-Unit / CC-Time600Allocated time in seconds
${CCA.Session-Id}Session-Id AVPomni-as01.epc...;1769299669873;325e2f2eDiameter session identifier
${CCA.Result-Code}Result-Code AVP2001CCA result (2001 = success)
${CCA.Auth-Application-Id}Auth-Application-Id AVP4Diameter application (4 = CC)
${CCA.CC-Request-Type}CC-Request-Type AVP1Request type (1=Initial)
${CCA.CC-Request-Number}CC-Request-Number AVP1Sequence number
${CCA.CC-Time}CC-Time AVP (if present)600Granted time quota
${CCA.Origin-Host}Origin-Host AVPocs01.epc.mnc001.mcc001.3gppnetwork.orgOCS host identifier
${CCA.Origin-Realm}Origin-Realm AVPepc.mnc001.mcc001.3gppnetwork.orgOCS realm
${CCA.Service-Information.Carrier-Select-Routing-Information}Service-Information → Carrier-Select-Routing-Information1408Carrier routing code from OCS
${CCA.Service-Information.Alternate-Charged-Party-Address}Service-Information → Alternate-Charged-Party-AddressNickTestAlternate 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