Reviewers (IPSD-3984, IPSD-4114):

Purpose

This document provides implementation and configuration guidance for partners wishing to integrate their connectors with the NSPS using docker microservices. Please note that this document does not cover topics such as monitoring, high availability, disaster recovery, or other operational and infrastructure-related aspects. These areas are considered out of scope and are the responsibility of the solution architect overseeing the connector’s deployment and maintenance.

Overview

The primary goal of the NSPS is to automate the provisioning and service updates of external network systems (e.g., HLR, PCRF) triggered by changes within PortaBilling. This includes some similar tasks such as service activation, deactivation, and configuration updates. NSPS is aimed to provide an alternative and light-weight tool for cloud provisioning to cover some basic flows. If some fancy workflow logic is needed - it's worth to consider PortaOne Workflows solution instead.

The connector is a service that receives events enriched by NSPS from PortaBilling, selects the necessary parameters, and sends them to the external system in the format required by that external system.

The example provided in this document is designed to run in GCP or AWS. However, the new connector can run in any cloud (Azure, AWS, OCI, etc.) or on-premises, as it is designed as a Docker microservice.

  • Hosting expenses for your connectors deployed in cloud platforms won't be covered by PortaOne.
  • There is a local deployment options, at no extra costs, to consider:


The interaction of the components is shown in the diagram below.


Implementation specific

Requirements

  • Authentication of all requests to the connector using a Bearer token.

  • Event payload schema parsing

  • Handling specific events (e.g., SIM activated, Plan changed, etc.)

  • Configurable settings for External API server:
    • API base URL with optional base path
    • API access credentials (depends on the system implementation. This can be either Basic Auth, Bearer Auth with a static token, or support for example JWT. The connector should work with access_tokens and refresh_tokens if the system requires it)
  • Response codes
    • 2XX for a successfully processed event (with optional JSON response body)
    • 4XX and 5XX codes for unsuccessfully processed events (with human-readable error explanation in response JSON body)

An example is available here - WTL HLR/HSS Connector.

The connector should provide 2 methods:

  1. GET health endpoint is needed to check the availability of the connector itself (this method should return just 200 OK with some optional response body).
  2. POST method is used by the NSPS system as a destination. The URL may or may not contain a path ( options such as "https://connector.com", "https://connector.com/api/events" are OK )

You can view or download an example of the OpenAPI specification below.

OpenAPI.json


{
  "openapi": "3.1.0",
  "info": {
    "title": "HLR/HSS Connector Microservice",
    "description": "Processes PortaBilling ESPF events (post-NSPS) and syncs with HLR/HSS Core system",
    "version": "1.0.0"
  },
  "paths": {
    "/health": {
      "get": {
        "summary": "Health Check",
        "description": "Health check endpoint",
        "operationId": "health_check_health_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {

                }
              }
            }
          }
        }
      }
    },
    "/process-event": {
      "post": {
        "summary": "Process Event",
        "description": "Process incoming PortaBilling ESPF event that has already been processed by NSPS",
        "operationId": "process_event_process_event_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Event accepted for processing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventResponse"
                },
                "example": {
                  "message": "Event accepted for processing"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "message": "Invalid access token",
                  "error": "Unauthorized",
                  "type": "authentication_error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "message": "Resource not found",
                  "error": "Not found",
                  "type": "validation_error"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "message": "Method not allowed",
                  "error": "Method not allowed",
                  "type": "validation_error"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "message": "Validation failed",
                  "error": "Validation failed",
                  "type": "validation_error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "message": "Too many requests to API Core",
                  "error": "Rate limit exceeded",
                  "type": "rate_limit_error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "message": "API Core HTTP error",
                  "error": "Internal server error",
                  "type": "service_error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "message": "Core service is not available",
                  "error": "Connection timeout",
                  "type": "connection_error"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "AccessPolicyAttribute": {
        "properties": {
          "group_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Group Name",
            "description": "The name used to group service policy attributes",
            "examples": [
              "lte.wtl"
            ]
          },
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name",
            "description": "The name of the service policy attribute",
            "examples": [
              "cs_profile"
            ]
          },
          "value": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Value",
            "description": "Service policy attribute value, comma-separated if multiple values",
            "examples": [
              "cs-policy"
            ]
          }
        },
        "type": "object",
        "title": "AccessPolicyAttribute",
        "description": "Access policy attribute model"
      },
      "AccessPolicyInfo": {
        "properties": {
          "i_access_policy": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Access Policy ID",
            "description": "The unique ID of the Service Policy",
            "examples": [179]
          },
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name",
            "description": "The name of the Access Policy",
            "examples": [
              "WTL integration"
            ]
          },
          "attributes": {
            "items": {
              "$ref": "#/components/schemas/AccessPolicyAttribute"
            },
            "type": "array",
            "title": "Attributes",
            "description": "The list of related service policy attribute values"
          }
        },
        "type": "object",
        "title": "AccessPolicyInfo",
        "description": "Access policy information model"
      },
      "AccountInfo": {
        "properties": {
          "bill_status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Billing status",
            "description": "The billing status of the account.\u003Cbr\u003E Possible values: \"open\" - the account is open;\u003Cbr\u003E \"suspended\" - the account is suspended. This status is available for debit accounts and can be set only automatically (by subscriptions assigned to the account);\u003Cbr\u003E \"inactive\" - the account is inactive. This status can be set only during account creation, manually or using the Account Generator;\u003Cbr\u003E \"terminated\" - the account is terminated. This status can be set only via the terminate_account method.",
            "examples": [
              "open"
            ]
          },
          "billing_model": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Account type",
            "description": "The account type.\u003Cbr\u003E An account can be a debit, credit, beneficiary, or voucher type.\u003Cbr\u003E A debit type is usually associated with prepaid cards and the account balance will show the available funds.\u003Cbr\u003E A credit account is invoiced for incurred costs.\u003Cbr\u003E A beneficiary account has a service configuration that only uses the balance, products, and quotas of a sponsor account.\u003Cbr\u003E A voucher-type account is used to refill debit or credit accounts; the balance of the voucher account is transferred to the main account.\u003Cbr\u003E Possible values: \"debit_account\", \"recharge_voucher\", \"credit_account\", \"alias\", \"beneficiary\".",
            "examples": [
              "credit_account"
            ]
          },
          "blocked": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Blocked",
            "description": "Indicates whether account's calls and access to the self-care interface is blocked",
            "examples": [false]
          },
          "email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email",
            "description": "The email address associated with the account",
            "examples": [
              "info@portaone.com"
            ]
          },
          "firstname": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "First name",
            "description": "The account owner's first name",
            "examples": [
              "John"
            ]
          },
          "i_account": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Account unique ID",
            "description": "The unique ID of the account",
            "examples": [277147]
          },
          "i_customer": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Customer ID",
            "description": "The ID of the customer record to which the account belongs",
            "examples": [6392]
          },
          "i_master_account": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Master Account ID",
            "description": "The ID of the parent account",
            "examples": [null]
          },
          "i_product": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Product ID",
            "description": "The ID for the account's product",
            "examples": [3774]
          },
          "i_vd_plan": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "VolumeDiscount Plan ID",
            "description": "The unique ID of the bundle assigned to the account individually",
            "examples": [1591]
          },
          "id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Id",
            "description": "The unique ID (PIN) of the account on the interface",
            "examples": [
              "380661310764@msisdn"
            ]
          },
          "lastname": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last name",
            "description": "The account owner's last name",
            "examples": [
              "Doe"
            ]
          },
          "phone1": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone1",
            "description": "The main phone number",
            "examples": [
              "380661310764"
            ]
          },
          "product_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Product Name",
            "description": "The name of the account's product",
            "examples": [
              "Pay as you go"
            ]
          },
          "status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Status",
            "description": "The current status of the account, based on its own status, the customer's status and other factors (like 'expiration time', 'activation time' and so on).\u003Cbr\u003E Possible values:\u003Cbr\u003E\"active\" - the account is active;\u003Cbr\u003E\"customer_exported\" - the account's customer is exported;\u003Cbr\u003E\"expired\" - the account expired;\u003Cbr\u003E\"quarantine\" - after being screened, the account was unable to supply valid credentials;\u003Cbr\u003E\"screening\" - the suspicious activity was detected for the account;\u003Cbr\u003E\"closed\" - the account is closed;\u003Cbr\u003E\"inactive\" - the account is inactive;\u003Cbr\u003E\"suspended\" - the account is suspended (applicable to debit accounts only);\u003Cbr\u003E\"customer_suspended\" - the account's customer is suspended;\u003Cbr\u003E\"customer_limited\" - services are limited for the account's customer;\u003Cbr\u003E\"customer_provisionally_terminated\" - the account's customer is provisionally terminated;\u003Cbr\u003E\"blocked\" - the account is blocked;\u003Cbr\u003E\"customer_blocked\" - the account's customer is blocked;\u003Cbr\u003E\"not_yet_active\" - the account's activation date has not yet come;\u003Cbr\u003E\"credit_exceeded\" - the account's balance is above the individual credit limit;\u003Cbr\u003E\"overdraft\" - the account's balance is overdrawn (applies to debit accounts only);\u003Cbr\u003E\"customer_has_no_available_funds\" - the account's customer funds are depleted;\u003Cbr\u003E\"customer_credit_exceed\" - the account's customer balance is above the credit limit;\u003Cbr\u003E\"zero_balance\" - the account's funds are depleted (applicable to debit accounts only);\u003Cbr\u003E\"customer_suspension_delayed\" - the account's customer suspension is lifted;\u003Cbr\u003E\"customer_limiting_delayed\" - the account's customer limitation is delayed;\u003Cbr\u003E\"frozen\" - the account's auto-payment is suspended due to repeated errors.",
            "examples": [
              "active"
            ]
          },
          "time_zone_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Time Zone Name",
            "description": "The name of the account's time zone. The UI equivalent of this field is the 'Time zone'. Examples: \"Europe/Prague\", \"America/Vancouver\", etc.",
            "examples": [
              "Europe/Kiev"
            ]
          },
          "zip": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Zip code",
            "description": "The postal (zip) code",
            "examples": [
              "40004"
            ]
          },
          "assigned_addons": {
            "items": {
              "$ref": "#/components/schemas/AddOnProduct"
            },
            "type": "array",
            "title": "Assigned Addons",
            "description": "The list of the account's add-on products"
          },
          "service_features": {
            "items": {
              "$ref": "#/components/schemas/ServiceFeature"
            },
            "type": "array",
            "title": "Service Features",
            "description": "The list of the service features"
          }
        },
        "type": "object",
        "title": "AccountInfo",
        "description": "Account information model"
      },
      "AddOnProduct": {
        "properties": {
          "addon_effective_from": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Addon Effective From",
            "description": "ISO datetime string - when the add-on product is activated",
            "examples": [
              "2025-05-16 12:59:46"
            ]
          },
          "addon_effective_to": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Addon Effective To",
            "description": "ISO datetime string - when the add-on product expires",
            "examples": [
              "2025-06-16 12:59:46"
            ]
          },
          "addon_priority": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Addon Priority",
            "description": "Priority of the addon: 0=main product, 10=low, 15=medium low, 20=medium, 25=medium high, 30=high",
            "examples": [10]
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Description",
            "description": "The internal product description",
            "examples": [null]
          },
          "i_product": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Product ID",
            "description": "The unique ID of the product",
            "examples": [3775]
          },
          "i_product_group": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Product Group ID",
            "description": "The unique ID of the product group to which the product belongs",
            "examples": [null]
          },
          "i_vd_plan": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "VolumeDiscount Plan ID",
            "description": "The unique ID of the bundle assigned to the product",
            "examples": [1591]
          },
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name",
            "description": "The product name",
            "examples": [
              "Youtube UHD"
            ]
          }
        },
        "type": "object",
        "title": "AddOnProduct",
        "description": "Addon product model"
      },
      "CardInfo": {
        "properties": {
          "i_account": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Account unique ID",
            "description": "The unique ID of the account to which the SIM card belongs",
            "examples": [277147]
          },
          "i_sim_card": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "SIM Card ID",
            "description": "The unique ID of the SIM card",
            "examples": [3793]
          },
          "iccid": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "ICCID",
            "description": "The Integrated Circuit Card ID. ICCIDs are stored in the SIM cards. Possible data format: a unique 18 to 22 digit code that includes the SIM card country, home network, and identification number.",
            "examples": [
              "89014103211118510720"
            ]
          },
          "imsi": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "IMSI",
            "description": "The unique International Mobile Subscriber Identity of the SIM card. Possible data format: a string of 6 to 15 digits.",
            "examples": [
              "001010000020349"
            ]
          },
          "msisdn": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "MSISDN",
            "description": "The Mobile Subscriber Integrated Services Digital Number, mobile number of the SIM card.",
            "examples": [
              "380661310764"
            ]
          },
          "status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Status",
            "description": "The status of the SIM card",
            "examples": [
              "active"
            ]
          }
        },
        "type": "object",
        "title": "CardInfo",
        "description": "SIM card information model"
      },
      "ESPFEvent": {
        "properties": {
          "event_type": {
            "type": "string",
            "title": "Event Type",
            "description": "The type of the event",
            "examples": [
              "SIM/Updated"
            ]
          },
          "variables": {
            "type": "object",
            "title": "Variables",
            "description": "All event variables passed as-is from original event"
          }
        },
        "type": "object",
        "required": [
          "event_type"
        ],
        "title": "ESPFEvent",
        "description": "Model representing incoming ESPF event"
      },
      "ErrorResponse": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message"
          },
          "error": {
            "type": "string",
            "title": "Error"
          },
          "type": {
            "$ref": "#/components/schemas/ErrorType"
          }
        },
        "type": "object",
        "required": [
          "message",
          "error",
          "type"
        ],
        "title": "ErrorResponse",
        "description": "Standard error response model"
      },
      "ErrorType": {
        "type": "string",
        "enum": [
          "validation_error",
          "authentication_error",
          "service_error",
          "connection_error",
          "rate_limit_error",
          "internal_error"
        ],
        "title": "ErrorType",
        "description": "Error types for the application"
      },
      "Event": {
        "properties": {
          "event_id": {
            "type": "string",
            "title": "Event Id",
            "description": "Unique identifier of the event",
            "examples": [
              "a3623086-24c2-47fb-a17f-929d9e542ed2"
            ]
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ESPFEvent"
              }
            ],
            "description": "Event data containing type and variables"
          },
          "handler_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Handler Id",
            "description": "ID of the handler processing this event",
            "examples": [
              "wtl"
            ]
          },
          "created_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Created At",
            "description": "When the event was created (ISO datetime string)",
            "examples": [
              "2025-06-09T17:44:21.207629+00:00"
            ]
          },
          "updated_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Updated At",
            "description": "When the event was last updated (ISO datetime string)",
            "examples": [
              "2025-06-09T17:44:22.125109+00:00"
            ]
          },
          "status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Status",
            "description": "Current status of the event",
            "examples": [
              "queued"
            ]
          },
          "pb_data": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PBData"
              },
              {
                "type": "null"
              }
            ],
            "description": "Simplified PortaBilling data with only essential fields"
          }
        },
        "type": "object",
        "required": [
          "event_id",
          "data"
        ],
        "title": "Event",
        "description": "Main event model"
      },
      "EventResponse": {
        "properties": {
          "message": {
            "type": "string",
            "title": "Message",
            "default": "Event accepted for processing"
          }
        },
        "type": "object",
        "title": "EventResponse",
        "description": "Event processing response"
      },
      "PBData": {
        "properties": {
          "account_info": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AccountInfo"
              },
              {
                "type": "null"
              }
            ],
            "description": "Account information from PortaBilling"
          },
          "sim_info": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CardInfo"
              },
              {
                "type": "null"
              }
            ],
            "description": "SIM card information from PortaBilling"
          },
          "access_policy_info": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AccessPolicyInfo"
              },
              {
                "type": "null"
              }
            ],
            "description": "Access policy information from PortaBilling"
          }
        },
        "type": "object",
        "title": "PBData",
        "description": "PortaBilling data for event enrichment."
      },
      "ServiceFeature": {
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name",
            "description": "The service feature name",
            "examples": [
              "netaccess_policy"
            ]
          },
          "effective_flag_value": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Effective Flag Value",
            "description": "The actual service feature flag value",
            "examples": [
              "Y"
            ]
          },
          "attributes": {
            "items": {
              "$ref": "#/components/schemas/ServiceFeatureAttribute"
            },
            "type": "array",
            "title": "Attributes",
            "description": "The list of service feature attributes"
          }
        },
        "type": "object",
        "title": "ServiceFeature",
        "description": "Service feature model"
      },
      "ServiceFeatureAttribute": {
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name",
            "description": "The service feature attribute internal name",
            "examples": [
              "access_policy"
            ]
          },
          "effective_value": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Effective Value",
            "description": "Service feature attribute value, comma-separated if multiple values",
            "examples": [
              "149"
            ]
          }
        },
        "type": "object",
        "title": "ServiceFeatureAttribute",
        "description": "Service feature attribute model"
      }
    },
    "securitySchemes": {
      "HTTPBearer": {
        "type": "http",
        "scheme": "bearer"
      }
    }
  }
}


Request handling

The ESPF event generated by EventSender contains a limited set of variables (event type, i_account, i_env, etc.) but external systems usually require more information (about the main product, add-ons, used quotas, etc.).

{
    "event_type":"SIM/Updated",
    "variables":{
      "i_env":3,
      "i_event":999999,
      "i_account":277147,
      "event_time":"2025-05-01 12:00:00"
    }
}
Therefore, the NSPS system makes API requests to PortaBilling in order to get more information about the account, SIM, etc., which relates to the created event. The NSPS adds all the necessary data and passes it to the connector.

Request body

NSPS sends the following request body to the connector (so the connector should accept it)

Field NameMandatoryDescription
event_idYesUnique identifier of the event.
dataYesEvent payload, represented by an instance of ESPFEvent.
pb_dataNoEnriched data from PortaBilling; includes account, SIM, and access policy info. Defaults empty.
handler_idNoOptional identifier of the handler assigned to process the event.
created_atNoTimestamp when the event was created.
updated_atNoTimestamp when the event was last enriched by NSPS.
statusNoStatus of event processing. Defaults to EventStatus.UNDEFINED.

The field called data  contains the generated ESPF event as it is. So if you don't need additional info from PortaBilling, you can only use this data.

The data structure for the pb_data is shown below. It includes all possible fields. The connector should contain logic to process only the parameters it needs, and unnecessary ones can be ignored.

For a more detailed description of the parameters, please refer to docs.portaone.com

Field PathMandatoryTypeDescription
pb_data.account_infoNoAccountInfoMain account-level data container
pb_data.account_info.bill_statusNostr

The billing status of the account.

Mapped values (originally single-letter codes): 

  • O → active
  • S → suspended
  • I → inactive
  • C → terminated

pb_data.account_info.billing_modelNostr

The account type.

Mapped values (originally int codes):

  • -1 → debit_account
  • 0 → recharge_voucher
  • 1 → credit_account
  • 2 → alias
  • 4 → beneficiary
pb_data.account_info.blockedNobool

Boolean conversion - Indicates whether account's calls and access

to the self-care interface is blocked (originally Y/N string,

converted Y → False, N → True)

pb_data.account_info.emailNostr

The email address associated with the account

pb_data.account_info.firstnameNostr

The account owner's first name

pb_data.account_info.i_accountNoint

Internal ID of the account. Visible in URL resource path on Admin UI page

pb_data.account_info.i_customerNoint

The ID of the customer record to which the account belongs

pb_data.account_info.i_master_accountNoint

The ID of the parent account

pb_data.account_info.i_productNoint

The ID for the account's product

pb_data.account_info.i_vd_planNoint

The unique ID of the bundle assigned to the account individually

pb_data.account_info.idNostr

Charging ID (MSISDN, ICCID or IMSI) of the account on the network. Visible as ID in Admin UI

pb_data.account_info.lastnameNostr

The account owner's last name

pb_data.account_info.phone1Nostr

The main phone number

pb_data.account_info.product_nameNostr

The name of the account's product

pb_data.account_info.statusNostr

The current status of the account based on factors such as "expiration time", "activation time" and so on.
Possible values:

  • active
  • customer_exported
  • expired
  • quarantine
  • screening
  • closed
  • inactive
  • customer_suspended
  • customer_limited
  • customer_provisionally_terminated
  • blocked
  • customer_blocked
  • not_yet_active
  • credit_exceeded
  • overdraft
  • customer_has_no_available_funds
  • customer_credit_exceed
  • zero_balance
  • customer_suspension_delayed
  • customer_limiting_delayed
  • frozen
    The UI equivalent of this field is the first string (text in bold) on the Edit Account -> Change status (button) -> "Change account status?" modal window
pb_data.account_info.time_zone_nameNostr

The name of the account's time zone. The UI equivalent of this field is the 'Time zone'

input field on the Edit Account → Personal info → General info → Web self-care panel.

Examples: "Europe/Prague", "America/Vancouver", etc.

pb_data.account_info.zipNostr

The postal (zip) code

pb_data.account_info.assigned_addonsNoList[AssignedAddon]

The list of the account's add-on products

pb_data.account_info.assigned_addons[].addon_effective_fromNostr

ISO datetime string - The date and time when the add-on product 

is activated for an account (originally Optional[str] datetime)

pb_data.account_info.assigned_addons[].addon_effective_toNostr

ISO datetime string - The date and time when the add-on product

assigned to an account expires (originally Optional[str] datetime)

pb_data.account_info.assigned_addons[].addon_priorityNoint

The priority level of the add-on product

pb_data.account_info.assigned_addons[].descriptionNostr

The internal product description

pb_data.account_info.assigned_addons[].i_productNoint

The unique ID of the product

pb_data.account_info.assigned_addons[].i_product_groupNoint

The unique ID of the product group to which the product belongs

pb_data.account_info.assigned_addons[].i_vd_planNoint

The unique ID of the bundle assigned to the product

pb_data.account_info.assigned_addons[].nameNostrThe product name
pb_data.account_info.service_featuresNoList[ServiceFeature]The list of service features
pb_data.account_info.service_features[].nameNostr

The service feature name

pb_data.account_info.service_features[].effective_flag_valueNostr

The actual service feature flag value. Possible values:

  • 'Y' → enabled
  • 'N' → disabled
pb_data.account_info.service_features[].attributesNoList[ServiceFeatureAttribute]

The list of service feature attributes

pb_data.account_info.service_features[].attributes[].nameNostr

The service feature attribute internal name

pb_data.account_info.service_features[].attributes[].effective_valueNostr

Service feature attribute value, comma-separated if multiple values

pb_data.sim_infoNoSimInfo

SIM card information from PortaBilling

pb_data.sim_info.i_accountNoint

The unique ID of the account to which the SIM card belongs

pb_data.sim_info.i_sim_cardNoint

The unique ID of the SIM card

pb_data.sim_info.iccidNostr

The Integrated Circuit Card ID

pb_data.sim_info.imsiNostr

The unique International Mobile Subscriber Identity of the SIM card

pb_data.sim_info.statusNostr

The status of the SIM card.

Possible values:

  • 'available' - the SIM card is available for assignment,
  • 'reserved' - reserved for the customer, but not yet assigned to the account,
  • 'used' - SIM card is being used by the account,
  • 'disposed' - SIM card is disposed and cannot be used.
pb_data.prev_sim_infoNoSimInfo

Previous SIM card information (used for SIM/Replaced events)

pb_data.prev_sim_info.i_accountNoint

The unique ID of the account to which the SIM card belongs

pb_data.prev_sim_info.i_sim_cardNoint

The unique ID of the SIM card

pb_data.prev_sim_info.iccidNostr

The Integrated Circuit Card ID

pb_data.prev_sim_info.imsiNostr

The unique International Mobile Subscriber Identity of the SIM card

pb_data.prev_sim_info.msisdnNostr

The Mobile Subscriber Integrated Services Digital Number

pb_data.prev_sim_info.statusNostr

The status of the SIM card

pb_data.access_policy_infoNoAccessPolicyInfo

Access policy information from PortaBilling

pb_data.access_policy_info.i_access_policyNoint

The unique ID of the Access Policy

pb_data.access_policy_info.nameNostr

The name of the Access Policy

pb_data.access_policy_info.attributesNoList[AccessPolicyAttribute]

The list of related service policy attribute values

pb_data.access_policy_info.attributes[].group_nameNostr

The name used to group service policy attributes

pb_data.access_policy_info.attributes[].nameNostr

The name of the service policy attribute

pb_data.access_policy_info.attributes[].valueNostr

Service policy attribute value, comma-separated if multiple values

pb_data.product_infoNoProductInfo

Product information from PortaBilling

pb_data.product_info.nameNostr

The product name

pb_data.product_info.descriptionNostr

The internal product description

pb_data.product_info.i_productNoint

The unique ID of the product

pb_data.product_info.i_vd_planNoint

The unique ID of the bundle assigned to the product

pb_data.product_info.addon_priorityNoint

The priority level of the add-on product.

  • 0 - main product
  • 10 - low
  • 15 - medium low
  • 20 - medium
  • 25 - medium high
  • 30 - high
pb_data.full_vd_counter_infoNoVDCounterInfoVD counter information from PortaBilling
pb_data.full_vd_counter_info[].discount_infoNostr

Short representation of the discount details (e.g., '0..60 - 100%')

pb_data.full_vd_counter_info[].dg_nameNostr

The name of the destination group the bundle item is applied to

pb_data.full_vd_counter_info[].i_vd_planNoint

The unique ID of the bundle

pb_data.full_vd_counter_info[].i_dest_groupNoint

The unique ID of the destination group the discount is applied to

pb_data.full_vd_counter_info[].allocated_amountNofloat

The total amount of service volume allocated to the customer/account 

in the current usage period

pb_data.full_vd_counter_info[].unitNostr

The name of the service unit or currency used

pb_data.full_vd_counter_info[].addon_priorityNoint

The priority level of the bundle item. Possible values:

  • -1 (no priority)
  • 0 (main product)
  • 10 (low)
  • 12 (bundled credit wallets)
  • 13 (bundled credits)
  • 14 (bundled credit preferred wallets)
  • 15 (medium low)
  • 20 (medium)
  • 25 (medium high)
  • 30 (high)
  • 255 (not part of a product)
pb_data.full_vd_counter_info[].vdp_nameNostr

The name of the bundle

pb_data.full_vd_counter_info[].i_vd_dgNoint

The unique ID of the bundle item

pb_data.full_vd_counter_info[].i_serviceNoint

The unique ID of the service

pb_data.full_vd_counter_info[].service_nameNostr

The name of the service the discount is applied to

pb_data.full_vd_counter_info[].remainingNofloat/str

The service volume that remains to be used to reach the current threshold.

Can be numeric value or string like 'N/A'

Request headers

NSPS sends the following headers to the connector used for tracing and debugging:

The connector should process them and add them to all log messages related to the processing of a specific request. If the headers were not delivered, the connector should generate unique values (for example, use UUID-compatible format).

Event types

Event typeTrigger conditionsMost used fields
SIM/Updated
  • SIM card details have been changed, e.g., the default PIN for inactive SIM cards has been changed.
  • The SIM card data specified in a custom field has been changed, e.g., the “Amazon” value is set in the “Marketplace” custom field.
  • A SIM card status has been changed, e.g., from “In use” to “Disposed”.
  • An account’s ID has changed. (Account.id has been updated.)
  • Another product was assigned to the account. An account’s product has changed. (Account.i_product has been updated.)

  • An account’s add-on product has been changed.
  • An add-on product has been removed from an account.
  • A new add-on product has been added for an account.
  • The user has topped up (“recharged”) their bundle of any service type to get more service volume before bundle renewal.
  • An account has exceeded its service usage quota (for services of all types except for Messaging and Internet access).
  • A service feature has been enabled/disabled for an account (the Service_Attribute_Values table has been updated).
  • Custom information (e.g., ID card) has been added or changed for an account. (The Custom_Field_Values table has been updated for the account.)
  • An account’s billing status has been changed or affected by the customer’s billing status. A customer’s status has changed. (Customers.bill_status has changed.)

  • An account has been blocked. (Accounts.blocked set to ‘Y.’)
  • An account has been unblocked. (Accounts.blocked set to ‘N.’)
  • Bundle is successfully activated for the first time (the bundle has never been activated for this account before).
  • Bundle is assigned to the account.
  • Bundle has expired (for bundles of the balance-dependant renewable type).
  • Grace period for the bundle is over (for bundles of the balance-dependant renewable type).
  • Bundle is removed from the account.
  • Bundle is automatically renewed for the account (for bundles of the balance-dependant renewable type).
  • Bundle is successfully activated after expiration (for bundles of the balance-dependant renewable type).

pb_data.sim_info.imsi

pb_data.account_info.i_account

pb_data.account_info.i_product

pb_data.account_info.bill_status

pb_data.account_info.blocked

pb_data.account_info.product_name

pb_data.account_info.assigned_addons[].i_product

pb_data.account_info.assigned_addons[].name

pb_data.account_info.assigned_addons[].i_vd_plan

pb_data.access_policy_info.attributes["name"=="cs_profile"].values[0]

pb_data.access_policy_info.attributes["name"=="eps_profile"].values[0]


SIM/CreatedA SIM card has been added to the inventory.

pb_data.sim_info.imsi

pb_data.account_info.i_account

SIM/DeletedA SIM card has been removed from the inventory.

pb_data.sim_info.imsi

pb_data.account_info.i_account

 SIM/ReplacedA SIM card has been assigned to, removed from or changed for an account.

pb_data.sim_info

pb_data.prev_sim_info

Configuration

Typically, a connector should contain parameters that are needed to connect to the external system, as well as some that are needed by the application itself. The simplest and most straightforward option is to pass environment variables. The connector must verify all requests by checking the Bearer token. Therefore, there is a need to set this token in the connector. It is worth setting it as an environment variable (API_TOKEN for example) and reading it in the code. So, when changing only the environment variable, you can quickly change the access token in case of its compromise.

Requirements:

  • configurable bearer token to access the service
  • configurable external system credentials 

Logging

Logging is an important part that allows debugging when necessary.

Requirements:

  • Logs should be written in JSON format (for search integration)
  • Log x-b3-traceid header to request_id field
  • Log x-request-id header to unique_id field

Logging these headers allows you to track all the events that occur with the event from its very beginning - entering the NSPS.

Error handling

  • Errors should be logged.
  • Each error response must contain a JSON body with an explanation of the reason for the error.

Deployment

Since the connector is designed as a Docker microservice, it can be deployed in any cloud, for example, using a cloud-specific utility. Below are examples of scripts that you can use, but you can write your own that are simpler or more complex to suit your needs.

Example for GCPdeploy as a Cloud Run. Official guide on how to deploy Cloud Run services you can find here.

A Cloud Run service URL typically follows the format: https://[TAG---]SERVICE_IDENTIFIER.run.app . SERVICE_IDENTIFIER  is a unique, stable identifier for the service, and the TAG refers to the traffic tag of the specific revision. The SERVICE_IDENTIFIER includes a random string and the region shortcut

https://[TAG---]SERVICE_NAME-PROJECT_NUMBER.REGION.run.app

where:

  • TAG is the optional traffic tag for the revision that you are requesting.
  • PROJECT_NUMBER is the Google Cloud project number.
  • SERVICE_NAME is the name of the Cloud Run service.
  • REGION is the name of the region, such as us-central1.

Example for AWS: deploy as an App Runner. Official guide on how to deploy App Runner services you can find here.

An App Runner service URL typically follows the format: https://[service-id].[region].awsapprunner.com  where service-id  is a unique identifier for your service and region is the AWS region where your service is hosted.

For example: https://abcd1234efgh.us-east-1.awsapprunner.com

Testing

You can test the application by sending an HTTP request.

First, you can make sure that the service is up and running by sending a health check request

$ curl https://[TAG---]SERVICE_NAME-PROJECT_NUMBER.REGION.run.app
{"status":"Healthy"}

Then you need to send a request with the expected data structure

curl -X POST https://[TAG---]SERVICE_NAME-PROJECT_NUMBER.REGION.run.app \
  -H "Authorization: Bearer your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "3e84c79f-ab6f-4546-8e27-0b6ab866f1fb",
    "data": {
      "event_type": "SIM/Updated",
      "variables": {
        "i_env": 1,
        "i_event": 999999,
        "i_account": 1,
        "curr_status": "used",
        "prev_status": "active"
      }
    },
    "pb_data": {
      "account_info": {
        "bill_status": "open",
        "billing_model": "credit_account",
        "blocked": false,
        "firstname": "Serhii",
        "i_account": 1,
        "i_customer": 6392,
        "i_product": 3774,
        "id": "79123456789@msisdn",
        "lastname": "Dolhopolov",
        "phone1": "",
        "product_name": "Pay as you go",
        "time_zone_name": "Europe/Prague",
        "assigned_addons": [
          {
            "addon_effective_from": "2025-05-16T12:59:46",
            "addon_priority": 10,
            "description": "",
            "i_product": 3775,
            "i_vd_plan": 1591,
            "name": "Youtube UHD"
          }
        ],
        "service_features": [
          {
            "name": "netaccess_policy",
            "effective_flag_value": "Y",
            "attributes": [
              {
                "name": "access_policy",
                "effective_value": "179"
              }
            ]
          }
        ]
      },
      "sim_info": {
        "i_sim_card": 3793,
        "imsi": "001010000020349",
        "msisdn": "79123456789",
        "status": "active"
      },
      "access_policy_info": {
        "i_access_policy": 179,
        "name": "Integration test",
        "attributes": [
          {
            "group_name": "lte.wtl",
            "name": "cs_profile",
            "value": "cs-pp-20250319"
          },
          {
            "group_name": "lte.wtl",
            "name": "eps_profile",
            "value": "eps-pp-20250319"
          }
        ]
      }
    },
    "handler_id": "hlr-hss-nsps",
    "created_at": "2025-03-12T16:47:30.443939+00:00",
    "updated_at": "2025-03-12T16:47:36.585885+00:00",
    "status": "received"
  }'


{"message":"Event processed successfully"}
 


What should be tested:

  • Auth
  • Response codes (2XX, 4XX, 5XX)
  • Changes to the external system

It is worth testing this service with some kind of external staging system. It is not recommended to use production immediately without being sure that the service works correctly.

Infrastructure Considerations

The default deployment of NSPS, PortaBilling, and Connector/Core is intended to run in a public internet environment, where services can communicate freely over the network. However, in specific cases, it may be necessary to restrict public access to components for security reasons.

At this time, we can provide a static IP address used by NSPS to make requests to both the Connector and PortaBilling.

VPN connectivity is not currently supported and is under consideration (DO-5364).