Posts and how to...

Many useful articles and instructions!

    How to extract the VLess server list from a HAPP subscription for vpn V2Ray and V2RayN apps, and connect devices that exceed the subscription limit

    Instruction: Happ Proxy Subscription Converter

    Parsing Happ Proxy Subscriptions for V2RayN / V2RayA / Clash / SingBox

    ⚠️ Important: This script is intended for educational purposes. Use only with a legal subscription and in accordance with your provider's terms of service.


    Table of Contents

    1. Purpose
    2. Requirements
    3. Installation
    4. Intercepting App Headers (Debug)
    5. Script Configuration
    6. File Security (.htaccess)
    7. Testing
    8. Connecting to VPN Clients
    9. Bypassing Device Limits
    10. Additional Converters
    11. Troubleshooting
    12. Security
    13. File Structure

    Purpose

    The happ_converter.php script solves the following tasks:

    Task Solution
    Mobile App Emulation Substitution of headers like User-Agent: Happ/3.13.0
    Bypass Device Verification Using custom X-Hwid, X-Real-Ip
    Subscription Decoding Automatic Base64 decoding → list of vless://
    ⚡ Caching Saving results for 3 hours to speed up loading
    Universality Works with V2RayN, V2RayA, Clash, SingBox

    ⚙️ Requirements

    • PHP 7.4+ with support for extensions:
      • curl
      • mbstring
      • json
    • Web Server: Apache (recommended) or Nginx
    • Write permissions in the script directory (for output.cache)
    • (Optional) mod_rewrite module for .htaccess

    Check cURL availability in PHP:

    php -m | grep curl
    # or create a file info.php with <?php phpinfo();

    Installation

    Step 1: Create the file happ_converter.php

    Copy the code below into a new file:

    <?php
    /**
     * Happ Proxy Subscription Converter (Lite + Cache)
     * For V2RayN / V2RayA
     * Version: 1.2
     */
    
    // ================= SETTINGS =================
    $subscriptionUrl = trim('https://subscription.web.tech/YOUR_LINK');
    
    // Headers of the Happ mobile application
    $headers = [
        'User-Agent: Happ/3.13.0',
        'X-Device-Os: Android',
        'X-Device-Locale: ru',
        'X-Device-Model: ELP-NX1',  // ← Replace with your model
        'X-Ver-Os: 15',              // ← Replace with your Android version
        'Accept-Encoding: gzip',
        'Connection: close',
        'X-Hwid: 74jf74nf8f4jr5je',  // ← Unique Device ID
        'X-Real-Ip: 101.202.303.404',
        'X-Forwarded-For: 101.202.303.404',
    ];
    
    $timeout = 30;                    // Request timeout (seconds)
    $cache_ttl = 10800;              // Cache lifetime: 3 hours (in seconds)
    $cache_file = __DIR__ . '/output.cache';
    // =============================================
    
    // ================= CACHE CHECK =================
    if (file_exists($cache_file)) {
        if ((time() - filemtime($cache_file)) < $cache_ttl) {
            header('Content-Type: text/plain; charset=utf-8');
            header('Cache-Control: no-cache, max-age=0');
            header('X-Cache: HIT');
            echo file_get_contents($cache_file);
            exit;
        }
    }
    
    // ================= REQUEST TO HAPP SERVER =================
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $subscriptionUrl,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_TIMEOUT        => $timeout,
        CURLOPT_ENCODING       => '',  // Auto-handle gzip
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error = curl_error($ch);
    curl_close($ch);
    
    // Error handling
    if ($error || $httpCode !== 200 || !$response) {
        // Fallback: if there is an old cache — serve it
        if (file_exists($cache_file)) {
            header('Content-Type: text/plain; charset=utf-8');
            header('X-Cache: STALE');
            echo file_get_contents($cache_file);
            exit;
        }
        http_response_code(502);
        die("Error: " . ($error ?: "HTTP $httpCode"));
    }
    
    // Decode Base64 (if needed)
    $decoded = base64_decode($response, true);
    $output = $decoded ?: $response;
    
    // Save to cache
    file_put_contents($cache_file, $output, LOCK_EX);
    
    // Serve result
    header('Content-Type: text/plain; charset=utf-8');
    header('Cache-Control: no-cache, max-age=0');
    header('X-Cache: MISS');
    echo $output;

    Intercepting App Headers (Debug)

    Why is this needed?
    If the provider updated the app and your headers are outdated, or if you want to use your own unique parameters (HWID, device model) — you need to intercept the actual headers sent by the Happ app.

    Step 1: Create the file debug_headers.php

    This script will save all incoming headers to a file requests.log:

    <?php
    /**
     * Debug: Logging incoming requests from Happ App
     * Saves all HTTP headers to requests.log
     */
    
    // Path to log file
    define('LOG_OUTPUT', __DIR__ . '/requests.log');
    
    // Format data for writing
    $data = sprintf(
        "[%s]\n%s %s %s\n\nHTTP HEADERS:\n",
        date('c'),
        $_SERVER['REQUEST_METHOD'],
        $_SERVER['REQUEST_URI'],
        $_SERVER['SERVER_PROTOCOL']
    );
    
    // Collect all HTTP headers
    foreach ($_SERVER as $name => $value) {
        if (preg_match('/^HTTP_/',$name)) {
            // Convert HTTP_HEADER_NAME to Header-Name
            $name = strtr(substr($name,5),'_',' ');
            $name = ucwords(strtolower($name));
            $name = strtr($name,' ','-');
    
            // Add to list
            $data .= $name . ': ' . $value . "\n";
        }
    }
    
    $data .= "\nREQUEST BODY:\n" . file_get_contents('php://input') . "\n";
    
    // Write to file (with locking)
    file_put_contents(LOG_OUTPUT, $data, FILE_APPEND|LOCK_EX);
    
    echo("OK!\n");

    Step 2: Configure the Happ App

    1. Open the Happ Proxy app settings
    2. Find the Subscription or Subscription URL section
    3. Enter the link to your server:
      https://your-site.ru/debug_headers.php
    4. Click Update Subscription or Sync

    Step 3: Check the file requests.log

    After updating the subscription in the app, the file requests.log will contain something like:

    [2026-03-15T10:30:45+00:00]
    GET /debug_headers.php HTTP/1.1
    
    HTTP HEADERS:
    Host: your-site.ru
    User-Agent: Happ/3.13.0
    X-Device-Os: Android
    X-Real-Ip: 101.202.303.404
    X-Device-Locale: ru
    X-Device-Model: ELP-NX1
    Connection: close
    X-Hwid: 74jf74nf8f4jr5je
    X-Ver-Os: 15
    X-Forwarded-For: 101.202.303.404
    Accept-Encoding: gzip
    
    REQUEST BODY:

    Step 4: Copy headers to happ_converter.php

    Take the values from requests.log and paste them into the main script:

    $headers = [
        'User-Agent: Happ/3.13.0',           // ← from log
        'X-Device-Os: Android',              // ← from log
        'X-Device-Locale: ru',               // ← from log
        'X-Device-Model: ELP-NX1',           // ← your model
        'X-Ver-Os: 15',                      // ← your Android version
        'Accept-Encoding: gzip',
        'Connection: close',
        'X-Hwid: 74jf74nf8f4jr5je',          // ← your HWID
        'X-Real-Ip: 101.202.303.404',        // ← can keep or generate
        'X-Forwarded-For: 101.202.303.404',  // ← must match X-Real-Ip
    ];

    Important: Delete the debug script after use!

    # After copying the headers:
    rm debug_headers.php
    rm requests.log  # or move to a safe location

    ⚠️ Do not leave debug_headers.php on the server!
    It can be used to obtain information about your requests and headers.


    ⚙️ Script Configuration

    Step 1: Replace the subscription link

    //  Before:
    $subscriptionUrl = trim('https://subscription.web.tech/YOUR_LINK');
    
    //  After (your real link from happ://add/...):
    $subscriptionUrl = trim('https://subscription.web.tech/qqwweeerrrttt45');

    If your link is in the format happ://add/https://..., remove the prefix happ://add/.

    Step 2: Configure headers

    Use data from requests.log (see section above) or leave defaults:

    $headers = [
        'User-Agent: Happ/3.13.0',
        'X-Device-Os: Android',
        'X-Device-Locale: ru',
        'X-Device-Model: ELP-NX1',   // your phone model
        'X-Ver-Os: 15',              // your Android version
        'Accept-Encoding: gzip',
        'Connection: close',
        'X-Hwid: 74jf74nf8f4jr5je',  // unique identifier
        'X-Real-Ip: 101.202.303.404',
        'X-Forwarded-For: 101.202.303.404',
    ];

    Step 3: Configure caching

    $cache_ttl = 10800;  // 3 hours in seconds
    // Can be changed:
    // 1 hour  = 3600
    // 6 hours = 21600
    // 12 hours = 43200

    File Security (.htaccess)

    To protect cache files, create .htaccess in the same folder:

    #  Deny direct access to service files
    <FilesMatch "\.(cache|meta|log|tmp)$">
        Require all denied
    </FilesMatch>
    
    # Hide all files starting with a dot
    <Files ~ "^\.">
        Require all denied
    </Files>
    <Files ".htaccess">
        Require all granted
    </Files>
    
    # Disable directory listing
    Options -Indexes
    
    # (Optional) Protect the script itself from direct access without parameters
    <Files "happ_converter.php">
        <If "%{QUERY_STRING} == ''">
            Require all denied
        </If>
    </Files>

    ⚠️ For Apache 2.2 replace Require all denied with Deny from all.


    Testing

    Test via terminal (curl)

    # 1️⃣ First request (cache created, X-Cache: MISS)
    curl -i "https://your-site.ru/happ_converter.php" | head -20
    
    # Expected response:
    # HTTP/1.1 200 OK
    # Content-Type: text/plain; charset=utf-8
    # X-Cache: MISS
    # vless://uuid@ip:port?...
    # vless://uuid@ip:port?...
    
    # 2️⃣ Repeat request (cache used, X-Cache: HIT)
    curl -i "https://your-site.ru/happ_converter.php" | grep "X-Cache"
    # Response: X-Cache: HIT
    
    # 3️⃣ Check .htaccess protection
    curl -I "https://your-site.ru/output.cache"
    # Response: HTTP/1.1 403 Forbidden 
    
    # 4️⃣ Check requests.log protection
    curl -I "https://your-site.ru/requests.log"
    # Response: HTTP/1.1 403 Forbidden 

    Test via browser

    1. Open: https://your-site.ru/happ_converter.php
    2. You should see a list of links like:
      vless://343e5f1c-a7b1-5c98-b215-d8e6104ffgt6@89.208.85.123:443?security=reality&type=xhttp&...
    3. If you see Error: Failed to fetch subscription — check headers and link.

    Connecting to VPN Clients

    V2RayN (Windows / Android)

    1. Open SubscriptionsAdd Subscription
    2. Paste the link:
      https://your-site.ru/happ_converter.php
    3. Type: V2Ray / VLESS (or Raw)
    4. Click Update → servers will appear in the list

    V2RayA (Linux / Docker)

    1. Open web interface → Subscriptions
    2. Click Add Subscription
    3. URL:
      https://your-site.ru/happ_converter.php
    4. Label: Happ Proxy
    5. Click Update → import configurations

    Clash Meta / Mihomo

    Script modification is required to output in YAML format.
    For now, you can use external converters (see section below).

    SingBox / Hiddify

    Similar to Clash — JSON output is needed.
    Temporary: copy vless:// links manually.


    Bypassing Device Limits

    The provider counts devices by the X-Hwid header. To connect more than 3–5 devices:

    Method 1: Generate a new HWID

    // Insert into script or run separately:
    echo bin2hex(random_bytes(16));  // 32 characters, e.g.: a1b2c3d4e5f6...

    Method 2: Pass HWID via URL

    1. In the script replace the line:

      'X-Hwid: 74jf74nf8f4jr5je',

      with:

      'X-Hwid: ' . ($_GET['hwid'] ?? '74jf74nf8f4jr5je'),
    2. Now you can connect with different HWIDs:

      https://your-site.ru/happ_converter.php?hwid=abc123def456...

    ⚠️ Important: Do not abuse! If the provider tracks by account, not just by HWID, this may lead to a ban.


    Additional Converters

    If you need Clash YAML or SingBox JSON format, use external tools:

    Converter Direction Link
    vless-xtls-converter vless:// → SingBox/Clash JSON Open
    amnezia_xkeen_converter JSON → vless:// Open
    VlessLinker (GitHub) Multi-format, open source Repository

    How to use:

    1. Download the list of vless:// from our script
    2. Paste into the converter
    3. Download the ready config for the desired client

    Troubleshooting

    Problem Possible Cause Solution
    Empty response / 502 Wrong link, headers or blocking Check $subscriptionUrl, compare headers with intercept, temporarily set CURLOPT_SSL_VERIFYPEER => false
    Cache not updating File output.cache locked or no permissions Delete file manually: rm output.cache, check permissions chmod 666
    Base64 not decoding Server returned plain text, not Base64 Add log: file_put_contents('debug.log', $response); and check content
    403 on .htaccess Module mod_rewrite disabled or AllowOverride None Enable in Apache config: AllowOverride All, restart server
    Too slow response Timeout too small or hosting lag Increase $timeout = 60, check server speed
    Links not importing to client Unsupported format Ensure client type is V2Ray / VLESS, not Trojan/VMess

    Debug: Enable logging

    Add to the beginning of the script:

    error_reporting(E_ALL);
    ini_set('display_errors', 1);
    ini_set('log_errors', 1);
    ini_set('error_log', __DIR__ . '/error.log');

    Security

    Mandatory

    • [ ] Do not publish the script in public GitHub with real tokens
    • [ ] Delete requests.log and debug.log after debugging
    • [ ] Delete debug_headers.php after intercepting headers!
    • [ ] Restrict access to the script (if it is in public access):
    // Simple basic authorization:
    if (!isset($_SERVER['PHP_AUTH_USER']) || $_SERVER['PHP_AUTH_USER'] !== 'admin') {
        header('WWW-Authenticate: Basic realm="Happ Converter"');
        header('HTTP/1.0 401 Unauthorized');
        exit('Access denied');
    }
    • [ ] Regularly update headers if Happ changes the app version
    • [ ] Use HTTPS for the domain where the script is hosted

    Auto-update headers (optional)

    If Happ updates frequently, you can move headers to a separate file config.php:

    // config.php
    return [
        'user_agent' => 'Happ/3.14.0',
        'device_model' => 'NEW-MODEL-2025',
        // ...
    ];
    
    // In the main script:
    $config = require 'config.php';
    $headers = [
        'User-Agent: ' . $config['user_agent'],
        // ...
    ];

    File Structure

    /public_html/
    ├── happ_converter.php    #  Main script
    ├── debug_headers.php     #  Header intercept (delete after use!)
    ├── .htaccess             #  Protection of service files
    ├── output.cache          # ️ Subscription cache (created automatically)
    ├── requests.log          #  Header intercept logs (delete after use!)
    ├── error.log             #  Error logs (when debugging enabled)
    ├── config.php            # ⚙️ Extracted settings (optional)
    └── README.md             #  This instruction

    Script Update

    1. Download new version of happ_converter.php
    2. Save your settings ($subscriptionUrl, headers)
    3. Replace file on server
    4. Clear cache: rm output.cache
    5. Check operation: curl -I https://your-site.ru/happ_converter.php

    Support and Questions

    If something doesn't work:

    1. Check logs: tail -f error.log
    2. Test request manually:
      curl -v \
        -H "User-Agent: Happ/3.13.0" \
        -H "X-Hwid: your_hwid" \
        "https://subscription.web.tech/your_link"
    3. Write in response:
      • Error text
      • PHP version (php -v)
      • Web server type (Apache / Nginx)