System Architecture
Services, data flow, and deployment topology
NWX is a network diagnostics platform with clients on macOS and Windows, a Go backend on Google Cloud Run, and a fleet management API for MSPs and enterprise IT teams.
System Diagram
+-----------------------+
| Partner Dashboard |
| partner.nwx.com (SPA)|
+----------+------------+
|
v
+------------------+ +--------+---------+ +-------------------+
| | | | | |
| NWX Client | POST | C2 API | read | Partner API |
| (macOS/Win) +----->+ (Go, Cloud Run) +<----+ (Go, Cloud Run) |
| | | | from | |
+--------+---------+ +--------+---------+ Fire-+--------+----------+
| | store |
| metrics/telemetry | writes | OAuth2
| v | client_credentials
| +--------+---------+ |
+--------------->+ | +-------v----------+
| Log Ingest | | |
| (Go, Cloud Run)| | Your System |
| | | (API client) |
+--------+---------+ +------------------+
|
writes |
+-------+-------+-------+
| | |
v v v
Cloud Firestore GCS Bucket
Logging (device reg) (hop metrics)
Services
NWX runs four independent Go services on Google Cloud Run, each in its own Docker image:
| Service | Purpose | Cloud Run Name |
|---|---|---|
C2 API (server/api) |
Client check-in, JWT issuance, update distribution | c2-production / c2-staging |
Log Ingest (server/logs) |
Telemetry event and hop metric ingestion | logs-production / logs-staging |
Partner API (server/partner) |
Fleet management API + web dashboard | partner-api-production / partner-api-staging |
Ops (server/ops) |
Internal tooling | ops-production |
Each service scales independently. Services share a JWT secret (environment variable) so tokens issued by C2 are accepted by Log Ingest.
Client Lifecycle
Install
The client is deployed via platform-native installer:
- macOS: Apple-notarized PKG (Fastlane pipeline), deployable via JAMF/Kandji/Mosyle/Munki
- Windows: Azure Trusted Signed MSIX with
.appinstallerauto-update
See macOS Deployment and Windows Deployment for full guides.
Check-in
On launch (and periodically), the client POSTs to the C2 API. This is an unauthenticated endpoint; the client identifies itself by a locally-generated stable UUID.
Request:
POST https://api.networkweather.com/v1/check-in
Content-Type: application/json
{
"clientId": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
"appVersion": "1.0.5.42",
"os": "macOS",
"osVersion": "15.3",
"arch": "arm64",
"channel": "stable",
"build": "42",
"mspId": "msp-example-1234",
"orgId": "org-acme-5678"
}
Response:
{
"serverTime": "2026-02-24T12:00:00Z",
"device": {
"registered": true,
"assignedMspId": "msp-example-1234",
"assignedOrgId": "org-acme-5678"
},
"update": {
"available": false
},
"config": {
"telemetry": {
"endpoint": "https://logs.networkweather.com/v1/ingest/logs",
"token": "eyJhbGciOiJIUzI1NiIs...",
"tokenExpiresAt": "2026-02-25T12:00:00Z"
},
"metrics": {
"endpoint": "https://logs.networkweather.com/v1/ingest/metrics",
"intervalSeconds": 300
},
"policyTtlSeconds": 3600
}
}
The C2 server:
- Issues a scoped JWT (24h expiry) with
logs:writeandmetrics:writeclaims - Returns telemetry and metrics endpoint URLs
- Checks for available updates and returns download artifacts if a newer version exists
- Upserts the device into the Firestore device registry (non-blocking)
Monitor
The client continuously monitors the network:
- Traceroute discovery reveals the full hop-by-hop path from device to destination
- Continuous ping (every 5s on AC power, 15s on battery) measures per-hop latency, loss, and jitter
- WiFi RF monitoring captures RSSI, noise floor, band, channel width, and TX rate
- Gateway fingerprinting identifies the router vendor, model, firmware, and security posture
- VPN detection identifies active tunnels, split vs. full tunnel mode, and provider
- Network segment attribution classifies each hop (WiFi, LAN, modem, ISP, VPN, transit, destination)
Ship Metrics
Every 5 minutes, aggregated per-hop statistics are batched and POSTed to the Log Ingest service as JWT-authenticated requests.
Hop metric record (one per hop per 5-minute window):
{
"type": "hop_metrics",
"ts": 1770410100520,
"hop": "192.168.1.1",
"ttl": 1,
"seg": "lan",
"dur": 300,
"n": 60,
"loss": 0.0,
"avg": 1.2,
"min": 0.8,
"max": 3.1,
"jit": 0.4,
"ctx": "0000053b",
"net": "wifi",
"asn": 7922,
"wifi": {
"rssi": -42,
"noise": -89,
"band": 5.0,
"ch_w": 80,
"tx_rate": 1200.0
}
}
Batches are submitted as JSON arrays (max 200 records or 512KB per POST). The server converts to gzip-compressed NDJSON and writes to GCS with Hive-style date partitioning: metrics/dt=2026-02-24/{clientId}-{epoch}-{rand}.ndjson.gz.
Network Segment Model
NWX classifies every hop in the traceroute path into a segment, enabling attribution of network problems to a specific layer:
[Device] --> WiFi --> LAN --> Modem --> ISP --> VPN --> Transit --> Destination
| Segment | Meaning | Example Hop |
|---|---|---|
wifi |
The wireless link itself | First hop when on WiFi |
lan |
Local network (router, switches) | 192.168.1.1 |
modem |
Cable modem, ONT, DSL modem | 10.0.0.1 (CGNAT) |
isp |
ISP access and transit | Hops matching client ASN |
vpn |
VPN tunnel hop | utun interface gateway |
transit |
Internet backbone, peering | Different ASN from ISP and destination |
destination |
Target service CDN or origin | Final hop |
This is how NWX answers "is it my WiFi, my ISP, or the service?" without the user needing technical knowledge.
Authentication Model
NWX uses three separate authentication mechanisms:
| Surface | Auth Method | Token Lifetime | Scopes |
|---|---|---|---|
| C2 check-in | Unauthenticated (client self-identifies by UUID) | N/A | N/A |
| Log Ingest | JWT Bearer (issued by C2 on check-in) | 24 hours | logs:write, metrics:write |
| Partner API | OAuth2 client_credentials grant |
1 hour | fleet:read, analytics:read, orgs:read, orgs:write |
The C2 check-in is intentionally unauthenticated. The client sends its UUID and receives a scoped JWT for metric submission. This means a client can check in without pre-provisioned credentials, which simplifies deployment. The mspId and orgId fields in the check-in request associate the device with a fleet.
The Partner API uses standard OAuth2 client_credentials flow. See Partner API Reference for full details.
Device Registry (Firestore)
Each device that checks in gets a document in the devices collection, keyed by clientId:
| Field | Type | Description |
|---|---|---|
clientId |
string | Stable UUID generated on first launch |
mspId |
string | MSP that manages this device |
orgId |
string | Organization within the MSP |
currentVersion |
string | App version (e.g., "1.0.5.42") |
os |
string | "macOS" or "Windows" |
osVersion |
string | OS version string |
arch |
string | "arm64" or "x64" |
channel |
string | "stable" or "beta" |
lastSeen |
timestamp | Server timestamp of last check-in |
checkInCount |
int | Incremented on each check-in |
serialNumber |
string (optional) | Hardware serial number |
assetId |
string (optional) | IT asset tag |
referrer |
string (optional) | Install attribution |
The Partner API reads from this collection to provide fleet visibility. The C2 API writes to it on every check-in.
Deployment Topology
Internet
|
+-- Cloud Run (us-central1)
| +-- c2-production (C2 API)
| +-- logs-production (Log Ingest)
| +-- partner-api-production (Partner API + Dashboard)
| +-- ops-production (Internal tooling)
| +-- (staging equivalents for each)
|
+-- Firestore (us-central1)
| +-- devices (device registry)
| +-- msps (MSP accounts)
| +-- orgs (organizations)
| +-- credentials (OAuth client credentials)
|
+-- Cloud Logging
| +-- checkins (C2 check-in events)
| +-- telemetry (client telemetry events)
|
+-- GCS (Cloud Storage)
| +-- nwx-metrics bucket (hop metrics, NDJSON.gz, 90-day retention)
|
+-- Cloudflare R2
+-- networkweather-pkgs (client installers, appcast.xml)
All NWX infrastructure runs on Google Cloud Platform. Client installers are distributed via Cloudflare R2 CDN.
Data Retention
| Data | Storage | Retention |
|---|---|---|
| Hop metrics (NDJSON.gz) | GCS bucket | 90 days |
| Telemetry events | Cloud Logging | 30 days (default) |
| Device registry | Firestore | Indefinite (updated on each check-in) |
| Check-in logs | Cloud Logging | 30 days (default) |