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 OK — SipDetailsResponse |
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 number —
ConstraintViolationException 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.
- Delete —
DELETE /{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 |