# TextMe Web App Reverse Engineering Report **Date:** January 28, 2026 **Target:** web.textme-app.com / api.textme-app.com --- ## Executive Summary TextMe is a free calling/texting app with a web interface. The web app uses **QR code-based authentication** (similar to WhatsApp Web), making automated API access challenging without a valid mobile app session. The backend is Django-based with JWT authentication, running on AWS infrastructure. **Viability Assessment:** ⚠️ **CHALLENGING** - Building an unofficial API wrapper is technically possible but requires: 1. A valid mobile app account to authenticate 2. Session token extraction from an authenticated session 3. Maintaining WebSocket connections for real-time events --- ## 1. Infrastructure Overview ### Domains Discovered | Domain | Purpose | |--------|---------| | `web.textme-app.com` | Web client (AngularJS SPA) | | `api.textme-app.com` | REST API server | | `webauth.textme-app.com` | QR code WebSocket auth | | `pubsub.textme-app.com` | Real-time messaging WebSocket | | `sentry.go-text.me` | Error tracking | | `ng.textme-app.com` | Offer wall / monetization | | `go-text.me` | Marketing / assets | ### Tech Stack - **Frontend:** AngularJS + Angular Material - **Backend:** Django (evident from CSRF token naming) - **Auth:** JWT tokens (`WWW-Authenticate: JWT realm="api"`) - **Real-time:** NGINX PushStream + WebSockets - **VoIP:** SIP.js for voice/video calls - **Hosting:** AWS (ELB detected) - **Analytics:** Google Analytics, Google Tag Manager - **Error Tracking:** Sentry --- ## 2. Authentication System ### Primary Auth Method: QR Code Login The web app uses a **WhatsApp Web-style QR code authentication**: 1. Web client generates a session UUID: `TMWEB-{uuid}` 2. Opens WebSocket: `wss://webauth.textme-app.com/ws/TMWEB-{uuid}/` 3. QR code encodes this session ID 4. Mobile app scans QR and authenticates the session 5. WebSocket receives auth token, web client stores JWT ### Secondary Auth Methods (in mobile app) - Email/password via `/api/auth-token/` - Social auth via `/api/auth-token-social/` (Google, Facebook) - Token refresh via `/api/auth-token-refresh/` ### CSRF Protection ``` Cookie Name: csrftoken Header Name: X-CSRFToken ``` ### JWT Token Flow 1. After successful auth, JWT token issued 2. Token sent in `Authorization: JWT ` header 3. Token refresh endpoint available for session extension --- ## 3. API Endpoints Discovered ### Authentication | Endpoint | Method | Description | |----------|--------|-------------| | `/api/auth-token/` | POST | Username/password login | | `/api/auth-token-social/` | POST | Social media OAuth login | | `/api/auth-token-refresh/` | POST | Refresh JWT token | | `/api/auth-token-voip-jwt/` | POST | Get VoIP-specific JWT | | `/api/register-device/` | POST | Register mobile device | ### User Management | Endpoint | Method | Description | |----------|--------|-------------| | `/api/user-info/` | GET | Get current user info | | `/api/users/` | GET | User lookup/search | | `/api/profile-picture/` | POST | Upload profile picture | | `/api/public-profile/` | GET | Public profile data | | `/api/settings/` | GET/PUT | User settings | | `/api/change-password/` | POST | Change password | | `/api/reset-password/` | POST | Reset password | ### Phone Numbers | Endpoint | Method | Description | |----------|--------|-------------| | `/api/phone-number/available-countries/` | GET | List available countries | | `/api/phone-number/choose/` | POST | Select a phone number | ### Messaging (Inferred) | Endpoint | Method | Description | |----------|--------|-------------| | `/event/conversation/` | WebSocket | Real-time conversation events | | `/api/group/` | GET/POST | Group chat management | | `/api/attachment/` | POST | Send attachments | | `/api/attachment/get-upload-url/` | POST | Get presigned upload URL | ### Calls | Endpoint | Method | Description | |----------|--------|-------------| | `/api/call/` | POST | Initiate/manage calls | ### Store/Monetization | Endpoint | Method | Description | |----------|--------|-------------| | `/api/store/stickers/` | GET | Available stickers | | `/api/store/stickers/packages/` | GET | Sticker packs | | `/api/inapp/transactions/` | GET/POST | In-app purchases | | `/api/pricing/` | GET | Pricing info | ### Misc | Endpoint | Method | Description | |----------|--------|-------------| | `/api/web/signup/` | POST | Web signup | | `/api/opengraph-resolver/` | POST | Resolve OpenGraph metadata | --- ## 4. Real-Time Communication ### PushStream (NGINX Module) Used for real-time messaging with multiple transport options: - WebSocket: `wss://pubsub.textme-app.com/ws/textme` - EventSource: `/ev` - Long Polling: `/lp` - Stream: `/sub` ### WebSocket Authentication ``` wss://webauth.textme-app.com/ws/TMWEB-{session-uuid}/ ``` ### VoIP (SIP.js) Voice/video calls use SIP protocol over WebSocket: - Codecs: OPUS, PCMA, PCMU - Transport: TCP/WebSocket --- ## 5. Security Measures Observed ### Anti-Bot Measures 1. **Google reCAPTCHA** - Integrated but may not be active for all endpoints 2. **403 Forbidden on suspicious requests** - POST to auth endpoints blocked 3. **AWS WAF** - Detected blocking patterns ### Rate Limiting - Not explicitly tested, but expected on auth endpoints ### CORS - Strict CORS policy, likely whitelisting only official domains --- ## 6. Mobile App Details - **Package:** `com.textmeinc.textme3` - **Facebook App ID:** `288600224541553` - **Available on:** iOS, Android - **Company:** TextMe, Inc. --- ## 7. Proof of Concept (Python) ```python """ TextMe Unofficial API Client - Proof of Concept WARNING: This is for educational purposes only. Requires a valid JWT token obtained from an authenticated session. """ import requests import websocket import json import uuid from typing import Optional, Dict, Any class TextMeAPI: BASE_URL = "https://api.textme-app.com" WS_AUTH_URL = "wss://webauth.textme-app.com/ws" WS_PUBSUB_URL = "wss://pubsub.textme-app.com/ws/textme" def __init__(self, jwt_token: Optional[str] = None): self.jwt_token = jwt_token self.session = requests.Session() self.session.headers.update({ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", "Accept": "application/json", "Content-Type": "application/json", }) if jwt_token: self.session.headers["Authorization"] = f"JWT {jwt_token}" def get_qr_session_id(self) -> str: """Generate a session ID for QR code authentication.""" return f"TMWEB-{uuid.uuid4()}" def wait_for_qr_auth(self, session_id: str) -> Optional[str]: """ Connect to WebSocket and wait for QR code scan. Returns JWT token on successful auth. """ ws_url = f"{self.WS_AUTH_URL}/{session_id}/" def on_message(ws, message): data = json.loads(message) if "token" in data: self.jwt_token = data["token"] self.session.headers["Authorization"] = f"JWT {self.jwt_token}" ws.close() return data["token"] ws = websocket.WebSocketApp( ws_url, on_message=on_message, ) ws.run_forever() return self.jwt_token def get_user_info(self) -> Dict[str, Any]: """Get current user information.""" response = self.session.get(f"{self.BASE_URL}/api/user-info/") response.raise_for_status() return response.json() def get_settings(self) -> Dict[str, Any]: """Get user settings.""" response = self.session.get(f"{self.BASE_URL}/api/settings/") response.raise_for_status() return response.json() def get_available_countries(self) -> Dict[str, Any]: """Get available countries for phone numbers.""" response = self.session.get(f"{self.BASE_URL}/api/phone-number/available-countries/") response.raise_for_status() return response.json() def refresh_token(self) -> str: """Refresh the JWT token.""" response = self.session.post( f"{self.BASE_URL}/api/auth-token-refresh/", json={"token": self.jwt_token} ) response.raise_for_status() data = response.json() self.jwt_token = data["token"] self.session.headers["Authorization"] = f"JWT {self.jwt_token}" return self.jwt_token def send_message(self, conversation_id: str, text: str) -> Dict[str, Any]: """ Send a text message. NOTE: Exact endpoint structure needs verification from network capture. """ # This is speculative - actual endpoint needs verification response = self.session.post( f"{self.BASE_URL}/api/conversation/{conversation_id}/message/", json={"text": text} ) response.raise_for_status() return response.json() # Usage Example (requires manual token extraction) if __name__ == "__main__": # Method 1: QR Code Auth (needs mobile app scan) api = TextMeAPI() session_id = api.get_qr_session_id() print(f"Scan this QR code with TextMe app:") print(f"Session: {session_id}") # token = api.wait_for_qr_auth(session_id) # Method 2: Use extracted token # token = "your_jwt_token_here" # api = TextMeAPI(jwt_token=token) # user = api.get_user_info() # print(user) ``` --- ## 8. Obstacles for Unofficial API ### Critical Barriers 1. **QR Code Only Auth** - No username/password on web, requires mobile app 2. **AWS WAF** - Blocks suspicious POST requests 3. **CSRF Protection** - Django CSRF tokens required 4. **Device Registration** - May require registered device ID ### Potential Workarounds 1. **Mobile App Interception** - Proxy mobile traffic to capture tokens 2. **APK Reverse Engineering** - Decompile Android app for API secrets 3. **Selenium/Puppeteer** - Automate web login with real QR scan 4. **Browser Extension** - Inject into authenticated session --- ## 9. Recommendations ### For Building an Unofficial API 1. **Start with mobile app** - Proxy traffic with mitmproxy/Charles 2. **Extract tokens** - Get JWT from authenticated session 3. **Map conversation endpoints** - Need network capture for message send/receive 4. **Handle real-time** - Implement WebSocket client for pubsub 5. **Token management** - Implement refresh token rotation ### Legal Considerations - Check TextMe Terms of Service before automated access - Unofficial APIs may violate ToS - Consider rate limiting to avoid account bans --- ## 10. Missing Information (Needs Further Research) - [ ] Exact message send endpoint structure - [ ] Conversation list endpoint - [ ] Message history endpoint - [ ] Contact management endpoints - [ ] Exact JWT payload structure - [ ] Device registration requirements - [ ] Rate limit thresholds --- ## Appendix: Raw Findings ### Sentry DSN ``` https://87d11b479de34e519af45bc5a47d4a9e@sentry.go-text.me/6 ``` ### Facebook App ID ``` 288600224541553 ``` ### Detected Server Headers ``` Server: nginx Server: awselb/2.0 WWW-Authenticate: JWT realm="api" ```