clawdbot-workspace/textme-api-research.md
2026-01-28 23:00:58 -05:00

357 lines
11 KiB
Markdown

# 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 <token>` 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"
```