Skip to content

Backend

Django REST Framework application in backend/. It owns the domain model, the Twilio integration, billing, and the Firestore mirror that feeds live updates to the clients.

Stack

  • Python 3.11 (backend/Dockerfile:2)
  • Django + DRF + djangorestframework-simplejwt, served over ASGI by uvicorn settings.asgi:application on port 8000 (backend/Dockerfile:31)
  • twilio, stripe, firebase-admin, django-minio-storage, django-auditlog
  • PostgreSQL; MinIO/S3 for media

Dependencies are unpinned

backend/requirements.txt pins only pytest and hypothesis. Every other package — Django included — resolves to whatever is current at image build time. The settings docstring mentions Django 4.2.2 (backend/settings/settings.py:4), but that is the version the file was generated with, not a constraint. Two builds a month apart can ship different Django minors.

Configuration

There is one settings module, settings/settings.py (429 lines), read through python-decouple's config(). DJANGO_SETTINGS_MODULE=settings.settings (backend/manage.py:9). There are no per-environment settings modules — environments differ only by environment variables.

In production, backend/entrypoint.sh loads around fifty values from Docker Swarm secrets named TWTLNKPROD_* (only when the variable is not already set), then runs collectstatic and migrate --noinput before handing off to the command (backend/entrypoint.sh:30-98). That TWTLNKPROD_* list is the authoritative inventory of production configuration keys.

See Configuration for the variable reference.

Application layout

INSTALLED_APPS contains exactly one project app: voip (backend/settings/settings.py:55-71). calls/ and google_hooks/ have an apps.py but are not installed — they are URL and logic modules with no models.

Root URL map (backend/settings/urls.py:6-12):

Prefix Module Responsibility
admin/ Django admin Staff tooling
api/v1/ voip.urls The whole authenticated REST API
webhooks/ voip.urls_wh Current Twilio webhooks (calls and SMS)
twilio/hooks/ calls.urls Legacy hooks — token, record, voicemail; most routes commented out
google/hooks/ google_hooks.urls Cron triggers

Inside voip/ there are roughly thirty sub-packages — company, companyUser, crm, sip, calls, messages, contact, notifications, redirects, stateTrace, twilio_charges, stripe_suscriptions and more — plus 105 migrations.

Two of them, voip/calls and voip/messages, follow a hexagonal layout (domain/, application/, infrastructure/, adapters/). The rest are flat api.py / models.py / features.py / serializer.py. Expect both styles.

backend/tools/ is not a Django app — it holds standalone operator scripts (set_webhooks.py, manual_twilio_charges_update.py, compare_charges.py).

Domain model

The models that carry the system:

Model Location Purpose
User voip/user/models.py:45 AUTH_USER_MODEL; email is the username. twilio_call_id is client:<email>
Company voip/company/models.py:12 The tenant. Holds record_calls / transcript_calls; isAdmin=True marks the super-tenant
CompanyUser voip/companyUser/models.py:7 User ↔ Company join carrying role and is_default — the pivot of tenancy and authorization
PhoneNumber voip/phoneNumber/models.py:6 A Twilio DID owned by a company; MAIN or REDIRECT, with priority
Contact voip/contact/models.py:26 Per-company address book; drives caller ID and call language
Call voip/crm/models.py:34 One Twilio call: sid, status, direction, duration, income, caller and responder
CallRecord voip/crm/models.py:137 Recording metadata plus storage and billing math
CallTranscription voip/crm/models.py:202 Whisper transcript and its per-minute price
CallTrace voip/call_trace/models/__init__.py:11 Live per-agent call state; FK to a User or a SipCredential
CallConfData / CallConfCalleeData voip/call_trace/models/call_conf_data.py:7,28 Conference identity and each callee leg
SipDomain voip/sip/models/domains.py:14 Twilio SIP domain: subdomain, voice URL, registration flag
SipCredentialsList voip/sip/models/credentials_list.py:18 Twilio credential list and its mapping SIDs
SipCredential voip/sip/models/credentials.py:14 One SIP username/password — a hardphone agent
StateTrace / HeartBeat voip/stateTrace/models.py:9,38 Agent online/offline presence, per platform
Message voip/messages/models.py:18 SMS with Twilio sid, status, price and segment count
AttentionHours voip/companyServices/models/attention_hours.py:10 Per-day open/close windows, closing messages, whitelist
CompanyRedirects / RulesGroup / Rule voip/redirects/models.py JSON-parameter rule engine for call redirection
Stripe_susc voip/stripe_suscriptions/models.py:6 One Stripe subscription per phone number
TwilioCallRecord and siblings voip/twilio_charges/models/ Raw Twilio billing mirror keyed by SID, for reconciliation

django-auditlog tracks User, Company, Contact, CompanyUser, Call, PhoneNumber, StateTrace, Extension and NewsLetter (voip/models.py:13-21).

Authentication and authorization

Authentication is JWT only: DEFAULT_AUTHENTICATION_CLASSES is [JWTAuthentication] (backend/settings/settings.py:174-176). Access tokens last one day, refresh tokens five, with rotation and blacklisting enabled, HS256 (backend/settings/settings.py:191-211).

Endpoint Purpose
POST /api/v1/login Email + password
POST /api/v1/login-firebase Verify a Firebase ID token, map it to a FirebaseUser
POST /api/v1/login/refresh Rotate the access token
POST /api/v1/tokenverify Validate a token
POST /api/v1/logout Blacklist the refresh token

Password reset is a three-step emailed-token flow.

DEFAULT_PERMISSION_CLASSES is not configured

DRF therefore falls back to AllowAny, and every protected view has to opt in by declaring permission_classes itself. Any view that forgets is public. Audit new views for this explicitly; see Known issues.

Roles live on CompanyUser.role: Owner, Admin, Staff (voip/companyUser/models.py:8-18). The permission classes that enforce them are IsAdminCompanyUser, IsAdminOrStaffCompanyUser and IsOwner (voip/companyUser/permissions.py), plus IsAdminCompany and CompanyViewIsAdminCompanyOrAdminUser (voip/company/permissions.py) which gate the super-tenant.

Twilio requests are authenticated by IsTwilio (backend/settings/twilio/permissions/is_twilio.py:9), which validates X-Twilio-Signature against a set of candidate URLs so that proxy rewrites do not break the signature check.

Integrations

  • Twilio — Programmable Voice and TwiML, Voice SDK access tokens, Elastic SIP domains and credential lists, Queues, Messaging, push credentials, and Usage Records for billing reconciliation. Covered in Telephony.
  • Firebase — FCM push notifications, Firestore as the live-update channel, and Firebase Auth for social login. Credentials come from PUSH_NOTIFICATIONS_CREDENTIAL (backend/settings/firebase/__init__.py:9).

    Note

    initialize_firebase() begins with an unconditional return (backend/settings/firebase/firebase_init.py:7). The real initialisation happens as an import side effect of settings/firebase/__init__.py, pulled in from settings/__init__.py:5. Do not "fix" the early return without tracing both paths.

  • Stripe — one subscription per phone number, metered usage events (incoming-message, outgoing-message, incoming-call-min, outgoing-call-min, storing-calls), a billing portal at /api/v1/stripe/portal and a webhook at /api/v1/stripe/webhook.

  • MinIO/S3, Whisper and Graylog, as described in Architecture overview.

Running it

See Getting started for the local loop and Deployment for how images reach the servers.