Developer FAQ

Link copied to clipboard

Authorization API

Link copied to clipboard

What exactly does the Authorization API do?

Link copied to clipboard

The Authorization API (POST /services/{user_id}/auth) does two things in one step:

  1. Checks whether the account has a sufficient balance or quota for the requested service units
  2. Reserves (locks) those units in PortaBilling for the duration of the lock_period

If the account has no remaining balance or quota at all, the request is rejected and nothing is locked. If the account has some balance but not enough to cover the full requested amount, PortaBilling locks the available quantity (locked_units in the response will be less than the requested units). For full request/response details, see the API reference.

Does authorization create an xDR? If so, is it kept open until the charge is applied?

Link copied to clipboard

No, authorization does not create an xDR. Submitting an authorization request creates a session lock – a temporary reservation that holds the requested units against the account’s balance.

An xDR is created when your application submits POST /charges. At that point, PortaBilling rates the actual consumed units, creates the xDR, and releases the session lock at the same time.

The session lock ends as soon as one of the following occurs, whichever comes first:

  • /charges is submitted – the lock is released, the consumed units are charged, and an xDR is created.
  • /unlock is submitted – the lock is released, no units are charged, and no xDR is created.
  • lock_period expires – the lock is released automatically, no units are charged, and no xDR is created.

What do locked_units and expires_at mean in the auth response?

Link copied to clipboard

These two fields describe the session lock created by the /auth request. A lock is a temporary reservation that holds the requested units against the account’s balance until it’s released – by a charge, an unlock, or its own expiration.

  • locked_units – the number of units PortaBilling actually locked. This may be less than the requested units if the account doesn’t have enough balance or quota to cover the full amount.
  • expires_at – the time at which the lock will expire if your application doesn’t release it earlier by submitting a charge or unlock request. At this time, PortaBilling automatically releases the reserved units.

Is there a timeout on locked funds?

Link copied to clipboard

Yes – the lock_period parameter (in seconds) you set in the authorization request determines how long PortaBilling holds the lock. The default is 300 seconds (5 minutes).

After the lock expires, PortaBilling automatically releases the lock and the funds become available again. There is no xDR created when the lock expires.

A subsequent charge request using the same billing_session_id after expiry will still create an xDR successfully – it will simply be processed without the benefit of the pre-authorized lock (the quota will be checked at charge time instead).

Always ensure your charge or unlock request arrives within the lock_period window. Set a lock_period for your workflow that is long enough to accommodate network delays and retries, but short enough not to hold funds unnecessarily.

Charges API

Link copied to clipboard

What does the Charges API do – does it finalize an existing xDR or create a new one?

Link copied to clipboard

The Charges API always creates a new xDR. Whether or not a prior /auth was called, calling POST /charges always creates a fresh xDR with the actual consumed units reported in the request.

The difference when prior authorization has been performed is not about xDR finalization – it is about the session lock:

  • With prior auth (same billing_session_id): the session lock is released as part of the same POST /charges call and the xDR is created using the actual consumed units
  • Without prior auth: the xDR is created without any lock to release

The xDR itself carries two charge outcomes: the amount billed to the account (your customer) and the cost you, as the service provider, owe the external vendor that supplied the underlying resource. These are tracked independently: for example, if the customer’s tariff has no applicable rate and the account charge fails, the vendor cost can still be recorded. The charged_units and charged_amount fields in the gateway’s response always describe only the customer-facing charge. For example, on an account charge failure, the response includes error_account, with charged_units and charged_amount returned as null. The vendor-side amount is tracked internally in PortaBilling and isn’t returned in the response.

What are billing_session_id, session_id, and item?

Link copied to clipboard
Field What it is How it’s used
billing_session_id PortaBilling’s unique identifier for this session PortaBilling uses this to identify and release the session lock created during authorization when the charge arrives. The same value must be used in both the /auth and /charges requests for the same session
session_id Your application’s unique identifier for this session Stored in the resulting charge record (xDR), so it can be used later to troubleshoot or investigate how a specific charge was applied
item The specific rate variant within the service Lets you charge different prices for different variants of the same service (e.g., “pay-per-view” vs. “premium pay-per-view” for video). PortaBilling matches this value against the rate options configured in the account’s tariff

How are billing_session_id and session_id generated?

Link copied to clipboard

session_id: If you don’t provide one, the gateway generates a random UUID v1-based identifier. You can provide any unique string that identifies the session on your side.

billing_session_id: The gateway generates it deterministically from your session_id using a UUID v3 (namespace-based hash). Before hashing, every non-alphanumeric character is stripped from session_id – only letters and digits are kept; hyphens, underscores, spaces, and other punctuation are removed:

billing_session_id = uuid3(NAMESPACE_DNS, "<session_id, alphanumeric characters only>.portaone.com") → formatted as four groups of 8 hexadecimal characters (0–9, A–F), separated by spaces, for example: "7B6AA461 4C5C21EF D47D5CBE AD918421"

This means the same session_id always produces the same billing_session_id – your auth and charge requests for the same session will automatically share the same billing_session_id as long as you use the same session_id in both.

If you provide an explicit billing_session_id in a valid format (four groups of 8 hexadecimal characters, e.g., “7B6AA461 4C5C21EF D47D5CBE AD918421”), it is used as-is. If you provide one in an invalid format, the gateway falls back to generating one from the session_id.

Best practice: Always provide a meaningful session_id – ideally, use an ID associated with the activity on your application side, such as the web request ID generated when a user submits a pay-per-view purchase. Then you can correlate activities in the application with the charges applied. Don’t rely on a randomly generated session_id when you need this correlation.

Do units represent the charged quantity?

Link copied to clipboard

units is the quantity you send (the amount you want to reserve when authorizing, or the amount you report as consumed when charging). The actual charged_units returned by PortaBilling may differ from the units you submit because of the tariff’s billing rules. See Why might charged_units differ from the units I sent?

What constitutes one unit depends on your PortaBilling tariff configuration:

Examples of services What units typically represent
Messaging (SMS) Number of message segments. For a multi-segment message, units should equal the number of segments, not the number of messages.
EV charging Kilowatt-hours (kWh)
AI inference Number of tokens
Voice Seconds or minutes (depending on tariff configuration)
Data Kilobytes or megabytes

Why might charged_units differ from the units I sent?

Link copied to clipboard

PortaBilling calculates charged_units by applying the tariff’s billing rules to the units value reported. For example:

  • If the tariff has a minimum billed duration of 60 seconds, PortaBilling charges 60 seconds even if only 10 seconds were consumed.
  • If the tariff rounds usage up to the nearest whole unit, 1.3 consumed units are billed as 2 units.

The charged_units field shows the quantity PortaBilling actually billed after applying these rules. The charged_amount field shows the resulting charge based on the configured rate.

How does the system determine the applicable cost per country or network? There are no MCC, MNC, or destination parameters

Link copied to clipboard

PortaBilling looks up pricing using the item value as a rate-lookup key against the rate options configured in the account’s tariff. That’s how country- or network-specific pricing works. There’s no separate MCC/MNC or country-code parameter, because the item itself carries that information.

Your application is responsible for setting item to a value that matches one of the rate options configured in the tariff:

  • For country-based SMS pricing, set item to the country dialing prefix (e.g., 44 for the UK, 1 for the US)
  • For network-based pricing, set item to the operator-specific prefix (e.g., 44ORANGE, 44VODAFONE)
  • For a flat rate across all countries/networks, use a single catch-all value (e.g., SMS)

The PortaBilling admin configures these rate options and their prices in the tariff. Your application needs to know which value to use based on the subscriber’s network or country. If item doesn’t match any configured rate option, the request fails with an “unmatched destination” error.

For messaging, a single message can be split into multiple segments. How is this handled?

Link copied to clipboard

Set units to the number of segments, not the number of messages. Each segment is billed as one unit.

Example: A 250-character SMS is split into 2 segments:

{ "service_type": "Quantity", "access_code": "SMS", "item": "44", "units": 2 }

The tariff is configured with a price per segment. PortaBilling multiplies the segment count by the rate to compute the charge.

If your platform bills per message (regardless of segments), set units: 1 and configure the tariff to price per message.

What does perform_authorization do?

Link copied to clipboard

perform_authorization controls whether the gateway checks the account’s available balance or quota before applying the charge.

Value Behavior
false The gateway charges immediately without first performing an authorization. Use this when you’ve already called /auth and have the billing_session_id ready to close, or when your use case accepts post-hoc billing (e.g., low-risk, low-value transactions). In the latter case, the charge may result in an overdraft if the account has insufficient balance or quota.
true The gateway first performs authorization for all service elements in the request. It proceeds with the charge only if every authorization succeeds. If any authorization fails, no charges are applied. Use this for a single-step authorize-then-charge flow, without a separate /auth call, when you need to guarantee that no service is delivered unless billing is confirmed in advance.

Does timestamp represent the billing time?

Link copied to clipboard

Yes. The timestamp field represents when the billable event occurred (not when the API request was made). PortaBilling stores this value in the xDR as the event time.

This is useful when charging is reported with a delay (e.g., a message was sent at 14:00 but the delivery confirmation arrived at 14:05 – set timestamp to 14:00 to record the correct billing time).

If the timestamp is not provided, the gateway uses the current time of the API request.

Unlock API

Link copied to clipboard

Does the Unlock operation release the locked funds?

Link copied to clipboard

Yes. The Unlock API (DELETE /services/{user_id}/unlock) sends a request to PortaBilling reporting zero consumed units, releasing the session lock. The reserved balance is returned to the account and becomes available immediately.

Because authorization itself never created an xDR, there’s no charge from that step to cancel. The account simply has its locked balance/quota freed.

The billing_session_id you provide must match the one returned by the /auth response for the same session. If there is no matching active session lock in PortaBilling (e.g., the lock has already expired or was already released by a prior charge/unlock), the request has no effect on the balance.

Balance and xDR queries

Link copied to clipboard

Do the Charge and Authorize APIs return the remaining balance?

Link copied to clipboard

No. Neither the authorization response nor the charge response includes the account’s remaining balance. The API returns:

  • Authorization: locked_units, expires_at, currency
  • Charge: charged_units, charged_amount

The balance is not exposed in the current REST API responses.

To obtain real-time balance: take the i_user value from the charge response (this is PortaBilling’s internal account identifier, called i_account in PortaBilling’s own APIs) and use it to query PortaBilling’s BillingAdmin API.

How do I find the xDR created by a charge request? The response does not include an xDR ID

Link copied to clipboard

The API response does not return a direct xDR ID. To locate the xDR in PortaBilling:

  1. Use the billing_session_id returned in the API response. Internally, PortaBilling stores this same value as a RADIUS field named h323-conf-id, and records it with each xDR.
  2. In PortaBilling’s web interface, open the Log viewer, search by h323-conf-id using your billing_session_id value.
  3. Alternatively, use the i_user value from the charge response (in PortaBilling, this value is named i_account) to look up all xDRs for the account and filter by timestamp.

For programmatic xDR lookup, use PortaBilling’s API with the h323_conf_id filter.

Can I retrieve the details on locked funds?

Link copied to clipboard

Not directly through the Web Charging Gateway API. The authorization response tells you how many units are locked (locked_units) and when the lock expires (expires_at), but there is no endpoint for retrieving active session locks later.

The application that requested a lock is responsible for storing that information and managing it.

To track active locks:

  • Store the billing_session_id, locked_units, and expires_at from the authorization response in your application.
  • In PortaBilling, you can open the Active sessions screen and filter by billing_session_id (PortaBilling stores this value as a RADIUS field named h323_conf_id) to confirm the session is still active. This screen shows session/call metadata, not the locked amount or expiration time.

Failure handling

Link copied to clipboard

What happens if xDR creation fails?

Link copied to clipboard

If PortaBilling cannot create the xDR (e.g., tariff configuration error, account issue), it returns an error in its response to the charge request. The gateway translates this to an element in failed_service_elements with error_account and/or error_vendor populated.

The API response will still have an HTTP status 200, but success will be false, and the service element will appear in failed_service_elements. No charge is recorded in PortaBilling.

Your application must check failed_service_elements on every charge response – a 200 OK does not guarantee that all charges succeeded.

Best practices for messaging flows

Link copied to clipboard

How to verify the customer has sufficient balance before sending a message?

Link copied to clipboard

Call POST /services/{user_id}/auth before sending the message:

{ "service_elements": [{ "session_id": "<your-message-id>", "service_type": "Quantity", "access_code": "SMS", "item": "<destination-prefix>", "units": <segment_count>, "lock_period": 120 }] }

If the response has a non-empty failed_service_elements, the account cannot cover the charge – do not send the message.

If authorization succeeds, the funds are locked for lock_period seconds. Proceed with sending the message within that window.

How to handle “charge on submission” vs “charge on delivery”?

Link copied to clipboard

Charge on submission (charge when the message is sent to the carrier):

Use POST /services/{user_id}/charges with perform_authorization: true. Authorization and charging happen in a single API call. The session lock is created and immediately released as the xDR is written – all in one Authorize→Accounting sequence inside the gateway.

Charge on delivery (charge when confirmed delivered):

  1. On submission: call POST /services/{user_id}/auth to lock funds. Keep the billing_session_id.
  2. On delivery confirmation: call POST /services/{user_id}/charges with perform_authorization: false, passing the same billing_session_id. The session lock is released and a new xDR is created with the actual charge.
  3. If delivery fails: call DELETE /services/{user_id}/unlock with the same billing_session_id. The session lock is released and funds are returned – no xDR is created.

How to release funds if delivery fails?

Link copied to clipboard

Call DELETE /services/{user_id}/unlock with the billing_session_id from the authorization response:

{ "billing_session_id": "<from auth response>", "session_id": "<your-message-id>", "service_type": "Quantity", "access_code": "SMS", "item": "<destination-prefix>" }

The session lock is immediately released and the funds are returned to the account balance. No xDR is created.

Do this within the lock_period window. After the lock expires, PortaBilling releases the funds automatically – so an explicit unlock after expiry has no effect.

How to adjust the charge based on the confirmed network (when network confirmation arrives after charging)?

Link copied to clipboard

This is a two-step process. The key constraint is that the item (the rate-lookup value that PortaBilling matches against the rate options configured in a tariff to determine price) must be fixed at the time of charging because it determines the tariff applied. You have two options:

Option A – Charge on confirmation (recommended): Use the authorize-on-submission, charge-on-delivery pattern. At authorization time, use a conservative item (e.g., a generic catch-all destination). At charge time, when you know the actual network, use the confirmed network-specific item:

  1. /auth with item: “SMS” (generic rate) – funds locked.
  2. Carrier confirms: message delivered via UK Orange (prefix 44ORANGE).
  3. DELETE /unlock with the authorization billing_session_id (releases the generic lock).
  4. /charges with item: “44ORANGE” (exact network rate), perform_authorization: false.
billing_session_id is a deterministic hash of session_id, so it stays the same throughout this flow – despite the /unlock in the middle, you don’t get a new one at /charges. You can use the same billing_session_id (or just keep reusing the same session_id) to correlate the authorization and the final charge in your records.

Option B – Charge at submission with correction xDR: Charge at submission using a conservative/average rate. When the actual network is confirmed and differs significantly, submit a correction using a second charge or a credit note through PortaBilling’s BillingAdmin API.

Option A is generally preferred as it produces a single accurate xDR per message and avoids correction complexity.

General

Link copied to clipboard

Why does the API return HTTP 200 even when a service element fails?

Link copied to clipboard

The HTTP status code reflects whether the gateway request itself was processed, not whether all billing transactions succeeded. A 200 means the gateway successfully communicated with PortaBilling and received a response.

Individual billing outcomes are reported per element in service_elements, failed_service_elements, and unprocessed_service_elements. Always inspect these arrays – do not rely on the HTTP status code alone to determine whether a charge was applied.

Appendix: PortaBilling terminology reference

Link copied to clipboard

For readers familiar with PortaBilling – the REST-facing fields and concepts used throughout this FAQ map to these PortaBilling terms internally.

REST-facing concept PortaBilling term
billing_session_id h323-conf-id – a PortaBilling identifier based on RADIUS
session_id call-id
item Rate code in the tariff
access_code Access code field in a product’s Usage charges section
timestamp Event-Timestamp
i_user i_account – PortaBilling’s internal account identifier

On this page

Docs for
What's new
Admin manuals
Handbooks
UI help
Developers documentation
  • Loading…