ZUCKZAPGO NATIVE MOBILE NETWORK ROUTE — IMPLEMENTATION GUIDE FOR LLMS ==================================================================== Purpose ------- Use this guide to implement a backend integration with ZuckZapGo's native, per-session mobile TCP egress. The application embeds tailscale.com/tsnet and does not require a manually managed SOCKS sidecar. Non-negotiable security model ----------------------------- 1. Mint a Tailscale auth key just in time on a trusted backend. Configure the key as single-use in Tailscale. Never send it to browser/mobile frontend. 2. Call ZuckZapGo over TLS. Never log request bodies for the route endpoint. 3. Only mode="mobile" is accepted. Set failClosed=true and tcpOnly=true. 4. Select the exit node by its StableNodeID, never hostname or current IP. 5. Treat routeVersion as a monotonic fencing token per ZuckZapGo user. 6. Generate a new idempotencyKey for every new version. Reuse it only when retrying the exact same request. 7. Never attempt a legacy proxy or direct connection when ready=false. 8. Native Calls/UDP is intentionally unavailable on mobile tcpOnly routes. 9. tcpOnly describes WhatsApp application egress through SOCKS. Tailscale may itself use UDP/WireGuard or DERP to carry that TCP, so it is not a promise that the host emits no UDP packets. Apply endpoint -------------- POST /session/network-route Headers: token: Content-Type: application/json Body (all fields required): { "mode": "mobile", "authKey": "", "exitNodeId": "nStableMobileExit01", "routeVersion": 1, "idempotencyKey": "route-2026-08-15-0001", "failClosed": true, "tcpOnly": true } Success envelope: { "code": 200, "data": { "mode": "mobile", "exitNodeId": "nStableMobileExit01", "desiredVersion": 1, "appliedVersion": 1, "failClosed": true, "tcpOnly": true, "status": "ready", "ready": true }, "details": "network route ready", "reconnectScheduled": false, "success": true } The response never contains authKey, idempotencyKey, SOCKS credentials, or a proxy URL. Do not write client code that expects those values. Read endpoint ------------- GET /session/network-route Header: token: Use ready only as reported by this endpoint. A stored status of ready is not enough by itself: ZuckZapGo also requires the live runtime and matching desired and applied versions. Fencing algorithm for your integration -------------------------------------- Maintain nextRouteVersion per ZuckZapGo user in your own transactional store. For a new desired route: A. atomically reserve version current+1; B. create a cryptographically random idempotency key; C. mint a single-use Tailscale auth key; D. send POST once; E. on transport timeout, retry the identical body and idempotency key; F. on 409 stale_version, GET state and reconcile; G. on blocked/503, do not connect directly; diagnose, then submit a higher version with a newly minted auth key and new idempotency key. Exact retry semantics: - same version + same route digest + same idempotency key: idempotent result; a ready version returns 200, while a blocked version repeats its safe 503 without another disconnect, node start, or enrollment-key consumption; - lower version: 409 network_route_stale_version; - same version with a changed exit node, flags, or idempotency key: 409 network_route_version_conflict. Pre-pairing provisioning ------------------------ POST /admin/users accepts an optional networkRoute object with the exact same schema. It must not be combined with proxyConfig. Example: { "name": "pilot-mobile-01", "token": "", "networkRoute": { "mode": "mobile", "authKey": "", "exitNodeId": "nStableMobileExit01", "routeVersion": 1, "idempotencyKey": "provision-pilot-0001", "failClosed": true, "tcpOnly": true } } If provisioning fails after the user row is created, ZuckZapGo returns the user ID and leaves it blocked. The exact Chatwoot success contract includes data.id, data.token, and a data.network_route object where ready=true and desiredVersion==appliedVersion: { "code": 201, "data": { "id": "stable-internal-user-id", "token": "", "network_route": { "mode": "mobile", "exitNodeId": "nStableMobileExit01", "desiredVersion": 1, "appliedVersion": 1, "failClosed": true, "tcpOnly": true, "status": "ready", "ready": true } }, "success": true } If route preparation fails after persistence, the exact 503 contract includes data.userId and blocked data.network_route, but deliberately omits token: { "code": 503, "data": { "userId": "stable-internal-user-id", "network_route": { "mode": "mobile", "exitNodeId": "nStableMobileExit01", "desiredVersion": 1, "appliedVersion": 0, "failClosed": true, "tcpOnly": true, "status": "blocked", "ready": false, "errorCode": "network_route_prepare_failed" } }, "error": "network_route_prepare_failed", "details": "user persisted in blocked state; submit a higher routeVersion with a new one-use auth key or delete this user", "success": false } For transactional Chatwoot provisioning, accept AddUser only when ready=true and desiredVersion==appliedVersion. On 503, execute DELETE /admin/users/{data.userId} with the admin token to roll back, or retain the blocked user and remediate using a higher route version if your trusted backend still has the session token. Never blindly retry AddUser: the token already exists and the enrollment key may be consumed. Status/error handling --------------------- 400 network_route_mode_invalid Your body attempted a mode other than mobile. Direct is never supported. 400 network_route_mobile_requires_fail_closed_tcp_only Set both booleans to true; do not offer a UI toggle that weakens them. 400 network_route_auth_key_invalid Mint a new single-use tskey-auth-* value on the trusted backend. 409 network_route_stale_version GET the current state and advance from desiredVersion. 409 network_route_version_conflict Do not mutate an existing version. Allocate a higher one. 503 network_route_prepare_failed The route remains blocked. Check exit-node advertisement/ACL, tailnet health, the persistent state volume, and TLS reachability through that exit node. 503 network_route_store_unavailable The route state cannot be trusted. Keep the session blocked and retry later. Operational invariants ---------------------- - Persist NETWORK_ROUTE_STATE_DIR on a private volume. Directories are 0700. - On restart, existing tsnet identity is restored without an auth key. - Loss of the volume causes restoration to remain blocked; submit a higher version with a new key after repairing storage. - Graceful shutdown closes runtimes without logging out, preserving identity. - User deletion logs out/revokes and removes only that user's derived state. - Each active route is a separate tsnet netstack/control client and therefore consumes non-trivial memory and goroutines. Load-test the real pilot count. - The built-in preflight proves SOCKS TCP + TLS through the configured exit route. After EditPrefs it runs up to 8 cancelable attempts (5 seconds each, exponential backoff from 250 ms to 2 seconds) and requires 2 consecutive successes, avoiding both immediate-convergence false negatives and flapping false positives. Independently verify carrier ASN/geolocation in the physical pilot. Full references --------------- OpenAPI 3.0: /api/spec.yml Human guide: /docs/network-route.md Status: GET /session/network-route Acceptance checklist for an integration ---------------------------------------- - AddUser 2xx is accepted only with data.id, data.token, ready=true, and equal desired/applied versions. - AddUser 503 with data.userId triggers DELETE /admin/users/{id} rollback, or a deliberate blocked-state remediation; it is never treated as success. - No log, trace, metric, database row, browser state, retry queue, or response contains authKey, SOCKS password, or proxy URL. - A blocked/unrestored route prevents connect and SetProxyAddress failure aborts bootstrap; prove there is no direct fallback. - Native Calls returns the explicit mobile TCP-only conflict for routed users. - Before production, use a real single-use key and physical mobile exit to verify carrier ASN/geolocation, then measure RSS/goroutines/DERP at pilot concurrency.