Skip to content

Phone Number Service

Manages the lifecycle of phone numbers and their LiveKit SIP inbound trunks. When a phone number is imported, the service auto-generates SIP credentials and creates a matching LiveKit SIP trunk. It provides SIP configuration details back to callers so PBX and trunk routes can be configured.

Attribute Value
Image communication-services/phone-number-service
Runtime Spring Boot 3.5.4 / Java 21
Namespace nexivo
Replicas 3
Database phone_numbers (PostgreSQL)

Tech Stack

Component Technology
Framework Spring Boot 3.5.4
Language Java 21
Database PostgreSQL
Cache Redis
SIP Integration LiveKit Server SDK 0.10.0 (SipServiceClient)
Migrations Flyway
Object Mapping ModelMapper
K8s Manifests Dekorate

Core Entity — PhoneNumber

Field Type Notes
id UUID PK
phoneNumber String Unique, E.164 format
region String Geographic region
status Enum ACTIVE / INACTIVE / PENDING / SUSPENDED / CONFIGURED / ERROR
livekitTrunkId String LiveKit SIP trunk ID
sipProvider String e.g. "twilio", "vonage"
importedDate OffsetDateTime Import timestamp
allowedNumbers List\<String> JSONB — allowed destination numbers
krispEnabled Boolean Krisp noise cancellation toggle
trunkName String Display name for the SIP trunk
regionPinningEnabled Boolean Enable LiveKit region pinning
pinnedRegion String Pinned LiveKit region identifier
sipCredentials JSONB { username, password, realm, sipUri, outboundProxy, port, tlsEnabled }
livekitConfig JSONB { apiKey, serverUrl, roomName, participantName, identity, metadata, grants, timeoutSeconds, headers }

Extends Trashable (soft delete — records are flagged, not physically removed).

SIP Credentials Detail

Field Format
username user_{phoneNumber} (stripped of + and -)
password 28-character SecureRandom string — guaranteed ≥1 uppercase, ≥1 lowercase, ≥1 digit, chars shuffled
realm Configured SIP realm
sipUri sip:{phoneNumber}@{livekit.sip-domain}
outboundProxy Outbound SIP proxy address
port SIP port
tlsEnabled TLS transport flag

REST API — /phone-numbers

Method Path Description Response
POST /import Import phone number; auto-generate credentials; create LiveKit SIP trunk 201 Created + Location header
GET /{phoneNumber} Full details by E.164 phone number 200 OK
GET / List all phone numbers (paginated) 200 OK
GET /status/{status} Filter by status enum 200 OK
GET /{phoneNumber}/sip-details SIP connection details 200 OKSipDetailsResponse
DELETE /{id} Soft-delete entity + best-effort LiveKit trunk deletion 204 No Content

SipDetailsResponse

{
  "username": "user_14155552671",
  "password": "Xk9mP2qR7nL4wZ8vYcD3aE5f",
  "sipUri": "sip:+14155552671@sip.example.com",
  "sipDomain": "sip.example.com",
  "sipTransport": "udp"
}

Import Flow

sequenceDiagram
    participant Client
    participant API as PhoneNumberController
    participant DB as PostgreSQL
    participant CredGen as SIP Credential Generator
    participant LK as LivekitSipService

    Client->>API: POST /phone-numbers/import { phoneNumber, trunkName, ... }
    API->>API: Validate E.164 format (^\\+[1-9]\\d{1,14}$)
    API->>DB: INSERT PhoneNumber (status=PENDING)
    DB-->>API: Saved entity

    API->>CredGen: generateCredentials(phoneNumber)
    Note over CredGen: username = user_{stripped}<br/>password = 28-char SecureRandom<br/>(≥1 upper, ≥1 lower, ≥1 digit, shuffled)
    CredGen-->>API: SipCredentials

    API->>DB: UPDATE sipCredentials on entity

    API->>LK: createSipInboundTrunk(trunkName, numbers, credentials)
    Note over LK: LiveKit Server SDK SipServiceClient<br/>POST to LiveKit REST API
    LK-->>API: SipInboundTrunkInfo { sipTrunkId }

    API->>DB: UPDATE status=CONFIGURED, livekitTrunkId=sipTrunkId
    DB-->>API: Updated entity

    API-->>Client: 201 Created + Location: /phone-numbers/{phoneNumber}

Error Handling During Import

  • Duplicate phone numberConstraintViolationException raised at DB insert; returns 409 Conflict.
  • LiveKit trunk creation failure — entity remains at status=ERROR; trunk creation failure is surfaced as an error response.
  • DeleteDELETE /{id} soft-deletes the DB record and calls deleteSipInboundTrunk(trunkId) on LiveKit. LiveKit deletion failure is non-fatal; the entity is still marked deleted.

LiveKit SIP Integration

The service uses SipServiceClient from the LiveKit Server SDK 0.10.0.

Operation Method Description
Create trunk createSipInboundTrunk(name, numbers, allowedNumbers, authCredentials) Registers phone number as an inbound SIP trunk; returns sipTrunkId
Delete trunk deleteSipInboundTrunk(trunkId) Called on phone number deletion; non-fatal on failure
Update trunk updateSipInboundTrunk(trunkId, options) Called when trunk configuration changes (e.g. allowed numbers, credentials)

Relationship to Other Services

Service How it uses Phone Number Service
Communication Scheduler References outboundTrunkId (= livekitTrunkId) on CallSchedule to route outbound calls through the correct SIP trunk
Call Service Uses trunk IDs for routing outbound SIP calls via LiveKit
LiveKit SIP Gateway Uses the trunk configuration registered here to receive and authenticate inbound SIP calls

Key Configuration

Property Description
spring.datasource.url jdbc:postgresql://…/phone_numbers
livekit.api-key LiveKit API key
livekit.api-secret LiveKit API secret
livekit.server-url LiveKit server URL
livekit.sip-domain SIP domain used when building sipUri
livekit.sip-transport SIP transport protocol (default: udp)
livekit.enabled true — enables LiveKit SIP trunk creation on import
multitenancy.enabled false