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

11 KiB

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)

"""
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
  • 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"