# How to Use the Login Method for Account Detection in Holehe: A Complete Guide

> Learn how to use Holehe's login method for effective account detection. This guide explains how Holehe checks for account existence without passwords.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: how-to-guide
- Published: 2026-08-30

---

**The login method in Holehe queries a service's authentication endpoint with an email address and analyzes the response for account-existence indicators without requiring a password.**

Holehe is an open-source OSINT tool by **megadose/holehe** that checks whether an email address is registered across hundreds of online services. Unlike public profile lookups, the **login method** exploits authentication endpoints—login forms, APIs, or mobile endpoints—to infer account existence based on subtle response differences. This technique is particularly valuable when services hide registration status from public view.

---

## Understanding the Login Method Architecture

Holehe's modular design separates detection strategies into distinct methods. Each module declares its approach via a class-level **`method`** attribute, allowing the core engine to route requests appropriately.

### How Methods Are Classified

| Method | Endpoint Targeted | Typical Indicator |
|--------|-------------------|-------------------|
| `login` | Authentication/login endpoint | "Incorrect password," "email exists," or session cookies |
| `register` | Sign-up/registration page | "Email already taken" |
| `forgot` | Password reset flow | "Email sent" vs "user not found" |
| `profile` | Public user profile | Direct 200 OK or 404 response |

The **login method** is preferred for stealth and coverage: it mimics legitimate user behavior, bypasses rate-limiting on public APIs, and works even when profile pages are disabled. When `method = "login"` is set, the module's `run()` coroutine crafts a POST request with the target email (and often a dummy password) to the service's login endpoint.

---

## Core Engine Flow in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)

The detection orchestration happens in **[`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)**. The `Holehe` class performs these steps:

1. **Module Discovery** – Scans `holehe/modules/` and its subdirectories for Python files containing detection classes.
2. **Method Filtering** – If `methods=["login"]` is passed, only modules with `method = "login"` are loaded.
3. **Client Initialization** – Creates an `httpx.AsyncClient` with rotation headers, timeout settings, and optional proxy configuration.
4. **Concurrent Execution** – Gathers all module `run()` coroutines with `asyncio.gather()`, respecting `max_connections` limits.
5. **Result Aggregation** – Returns standardized dictionaries with `status` (`FOUND`, `NOT_FOUND`, `UNKNOWN`), `message`, and metadata.

```python

# From holehe/core.py - simplified orchestration flow

async def run(self):
    client = httpx.AsyncClient(
        headers=self.rotation_headers,
        timeout=self.timeout,
        proxies=self.proxy
    )
    tasks = [
        module.run(client, self.email) 
        for module in self.loaded_modules 
        if module.method in self.target_methods
    ]
    results = await asyncio.gather(*tasks)
    return self._normalize(results)

```

---

## Login Method Implementation Examples

Each login-method module follows a consistent pattern: define the `url`, build request data, post to the endpoint, and parse the response for existence signals.

### Snapchat Module ([`holehe/modules/social_media/snapchat.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/snapchat.py))

The Snapchat implementation demonstrates a typical login-method flow against an internal API:

```python

# holehe/modules/social_media/snapchat.py

import httpx

class Snapchat:
    name = "snapchat"
    method = "login"
    url = "https://accounts.snapchat.com/accounts/merlin/login"

    @staticmethod
    async def run(client: httpx.AsyncClient, email: str) -> dict:
        # Snapchat requires specific headers and a CSRF token

        headers = {
            "Content-Type": "application/x-www-form-urlencoded",
            "X-Requested-With": "XMLHttpRequest"
        }
        
        data = {
            "username": email,
            "password": "ThisIsADummyPassword123!",  # Intentionally wrong

            "continue": "/"
        }
        
        resp = await client.post(Snapchat.url, data=data, headers=headers)
        json_resp = resp.json()
        
        # Snapchat returns specific error codes for existing vs non-existing

        if json_resp.get("status_code") == "login_incorrect_password":
            return {
                "status": "FOUND",
                "message": "Account exists (invalid password response)"
            }
        elif json_resp.get("status_code") == "login_invalid_user":
            return {
                "status": "NOT_FOUND",
                "message": "No account with this email"
            }
        return {"status": "UNKNOWN", "message": "Ambiguous response"}

```

**Key insight:** Snapchat distinguishes between "user not found" and "wrong password" at the API level—exactly the leak the login method exploits.

### Patreon Module ([`holehe/modules/social_media/patreon.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/patreon.py))

Patreon uses a GraphQL endpoint for login, requiring JSON payloads:

```python

# holehe/modules/social_media/patreon.py

import httpx
import json

class Patreon:
    name = "patreon"
    method = "login"
    url = "https://www.patreon.com/api/login"

    @staticmethod
    async def run(client: httpx.AsyncClient, email: str) -> dict:
        payload = {
            "data": {
                "type": "user",
                "attributes": {
                    "email": email,
                    "password": "WrongPasswordForDetection123"
                }
            }
        }
        
        resp = await client.post(
            Patreon.url,
            json=payload,
            headers={"Content-Type": "application/vnd.api+json"}
        )
        
        # Patreon returns 400 with specific error structure for existing accounts

        if resp.status_code == 400:
            errors = resp.json().get("errors", [])
            for error in errors:
                if error.get("code") == "invalid_password":
                    return {"status": "FOUND", "message": "Account exists"}
                if error.get("code") == "email_not_found":
                    return {"status": "NOT_FOUND"}
        
        return {"status": "UNKNOWN"}

```

### WordPress Module ([`holehe/modules/cms/wordpress.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/cms/wordpress.py))

For self-hosted WordPress sites, the login method targets [`wp-login.php`](https://github.com/megadose/holehe/blob/main/wp-login.php):

```python

# holehe/modules/cms/wordpress.py

import httpx
from urllib.parse import urljoin

class Wordpress:
    name = "wordpress"
    method = "login"
    # Base URL provided per-site; shown as template

    url_template = "{base}/wp-login.php"

    @staticmethod
    async def run(client: httpx.AsyncClient, email: str, base_url: str) -> dict:
        login_url = Wordpress.url_template.format(base=base_url)
        
        data = {
            "log": email,
            "pwd": "DummyPasswordDetection",
            "wp-submit": "Log In"
        }
        
        resp = await client.post(login_url, data=data)
        
        # WordPress redirects to wp-admin on successful login (we won't succeed)

        # But error messages reveal existence:

        if "Unknown username" in resp.text or "Invalid username" in resp.text:
            return {"status": "NOT_FOUND", "message": "Username/email not recognized"}
        if "The password you entered" in resp.text:
            return {"status": "FOUND", "message": "Valid user, wrong password"}
        
        return {"status": "UNKNOWN"}

```

---

## Using the Login Method: Command-Line Interface

The **`holehe`** CLI provides straightforward access to method-specific scanning.

### Basic Email Check (All Methods)

```bash
holehe alice@example.com

```

This runs **all** detection methods—login, register, forgot, and profile—sequentially per service.

### Restrict to Login Method Only

```bash
holehe alice@example.com --method login

```

Or using the shorthand:

```bash
holehe alice@example.com -m login

```

**Performance benefit:** Login-only scans typically complete 3-4x faster because they skip registration-page parsing and profile enumeration.

### Multiple Methods Selection

```bash
holehe alice@example.com -m login -m forgot

```

### Output Formatting with Login Results

```bash
holehe alice@example.com --method login --output json > results.json

```

The JSON output includes method attribution for each finding:

```json
{
  "snapchat": {
    "status": "FOUND",
    "method": "login",
    "message": "Account exists (invalid password response)",
    "timestamp": "2024-01-15T09:23:11Z"
  },
  "patreon": {
    "status": "NOT_FOUND",
    "method": "login",
    "message": "No account with this email"
  }
}

```

---

## Programmatic Usage: Python API

For integration into larger OSINT pipelines, import the `Holehe` class directly and constrain methods programmatically.

### Basic Programmatic Call

```python
import asyncio
from holehe.core import Holehe

async def check_login_method(email: str):
    """
    Check email against all services using login-method detection only.
    """
    scanner = Holehe(
        email=email,
        methods=["login"],           # Restrict to login method

        max_connections=10,          # Concurrent requests

        timeout=15.0
    )
    
    results = await scanner.run()
    return results

# Execute

output = asyncio.run(check_login_method("alice@example.com"))

# Process results

for service, data in output.items():
    if data["status"] == "FOUND":
        print(f"[FOUND] {service}: {data['message']}")

```

### Customizing the HTTP Client

```python
from holehe.core import Holehe
import httpx

async def check_with_proxy(email: str, proxy_url: str):
    # Holehe accepts a pre-configured client or proxy string

    scanner = Holehe(
        email=email,
        methods=["login"],
        proxy={"http://": proxy_url, "https://": proxy_url},
        rotation_headers={
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.0"
        }
    )
    return await scanner.run()

```

### Handling Rate Limits and Retries

```python
from holehe.core import Holehe
import asyncio

async def resilient_check(email: str, retries: int = 3):
    for attempt in range(retries):
        try:
            scanner = Holehe(
                email=email,
                methods=["login"],
                max_connections=5,  # Lower concurrency to avoid bans

                timeout=30.0
            )
            return await scanner.run()
        except httpx.ConnectTimeout:
            await asyncio.sleep(2 ** attempt)  # Exponential backoff

    
    raise RuntimeError("Maximum retries exceeded")

```

---

## Building Custom Login-Method Modules

To extend Holehe with new services, create a module following the established pattern.

### Module Template

```python

# holehe/modules/newservice/acmecorp.py

import httpx

class AcmeCorp:
    # Required: human-readable service name

    name = "acmecorp"
    
    # Required: method classification

    method = "login"
    
    # Required: endpoint URL

    url = "https://api.acmecorp.com/v2/auth/login"

    @staticmethod
    async def run(client: httpx.AsyncClient, email: str) -> dict:
        """
        Execute login-method detection for AcmeCorp.
        
        Returns dict with keys:
            - status: "FOUND" | "NOT_FOUND" | "ERROR" | "UNKNOWN"
            - message: Human-readable explanation
            - metadata: Optional additional data
        """
        # Build payload with email and deliberately wrong password

        payload = {
            "email": email,
            "password": "ThisIsNotARealPassword123",
            "device_id": "holehe-detection"
        }
        
        try:
            resp = await client.post(
                AcmeCorp.url,
                json=payload,
                headers={"Content-Type": "application/json"}
            )
            
            # Parse service-specific existence indicators

            if resp.status_code == 200:
                # Unexpected success—shouldn't happen with wrong password

                return {
                    "status": "UNKNOWN",
                    "message": "Unexpected successful authentication"
                }
            
            if resp.status_code == 401:
                error_code = resp.json().get("error_code")
                
                if error_code == "INVALID_CREDENTIALS":
                    # Service confirmed email exists but password wrong

                    return {
                        "status": "FOUND",
                        "message": "Account exists (invalid credentials)"
                    }
                elif error_code == "USER_NOT_FOUND":
                    return {
                        "status": "NOT_FOUND",
                        "message": "No account registered with this email"
                    }
            
            # Non-standard response

            return {
                "status": "UNKNOWN",
                "message": f"Unexpected status code: {resp.status_code}"
            }
            
        except httpx.RequestError as exc:
            return {
                "status": "ERROR",
                "message": f"Network error: {str(exc)}"
            }

```

### Module Discovery

Place the file in any subdirectory of `holehe/modules/`:

```

holehe/
├── modules/
│   ├── social_media/
│   ├── shopping/
│   └── your_category/          # Create as needed

│       └── acmecorp.py         # Your new module

```

The core engine automatically discovers classes with `method` attributes—no registration required.

---

## Security and Ethical Considerations

The login method generates **failed authentication attempts** against target services. Consider these implications:

- **Rate limiting**: Services may throttle or block IPs after repeated login attempts. Use proxy rotation and conservative concurrency.
- **Account lockouts**: Some services lock accounts after N failed logins. The dummy password technique generally avoids this, but verify per-service behavior.
- **Terms of Service**: Automated login attempts may violate ToS. Ensure compliance with applicable laws and service agreements.
- **False positives**: Services changing error messages can cause misclassification. Monitor module reliability and update parsers accordingly.

Holehe mitigates some risks through:
- **Non-destructive probing**: Never attempts credential stuffing with real passwords
- **Configurable delays**: Built-in jitter between requests via `asyncio.sleep()`
- **Header rotation**: Mimics diverse browser profiles to avoid fingerprinting

---

## Summary

- **The login method** queries authentication endpoints with an email and dummy password, inferring account existence from error responses.
- **Core implementation** lives in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), which filters modules by their `method` attribute and orchestrates concurrent `httpx` requests.
- **Key modules**: [`snapchat.py`](https://github.com/megadose/holehe/blob/main/snapchat.py), [`patreon.py`](https://github.com/megadose/holehe/blob/main/patreon.py), and [`wordpress.py`](https://github.com/megadose/holehe/blob/main/wordpress.py) demonstrate distinct login-method patterns—form POSTs, JSON APIs, and CMS endpoints.
- **CLI usage**: `holehe email@example.com -m login` restricts scans to login-method modules for speed and stealth.
- **Python API**: Instantiate `Holehe(email=..., methods=["login"])` for programmatic integration with custom clients and proxies.
- **Extensibility**: Create new modules by defining `name`, `method = "login"`, `url`, and an async `run()` coroutine following the standardized return format.

---

## Frequently Asked Questions

### What is the difference between the login method and the register method in Holehe?

**The login method targets existing user authentication endpoints, while the register method checks account creation flows for "email already taken" messages.** Login methods are generally more reliable because they directly interact with a service's primary identity system rather than secondary registration pages, which some services disable or protect with CAPTCHAs. However, register methods can detect accounts even when login endpoints return generic errors for both missing and existing users.

### Why does Holehe use a dummy password for login detection?

**A deliberately incorrect password triggers the "wrong password" response—proof the account exists—without risk of actual authentication.** Services rarely distinguish between "user not found" and "wrong password" in client-side code, but server responses (JSON error codes, HTTP headers, or redirect patterns) often leak this information. The dummy password approach, implemented across modules like [`snapchat.py`](https://github.com/megadose/holehe/blob/main/snapchat.py) and [`patreon.py`](https://github.com/megadose/holehe/blob/main/patreon.py), exploits this information asymmetry safely.

### Can I combine the login method with proxy rotation for large-scale scans?

**Yes—pass a proxy configuration to the `Holehe` constructor or set environment variables for `httpx` to consume.** The core engine accepts `proxy={"http://": url, "https://": url}` or rotates clients externally. For CLI usage, export `HTTP_PROXY` and `HTTPS_PROXY` before invoking `holehe`. Recommended practice: use residential proxies, limit `max_connections` to 5-10, and implement exponential backoff for 429/403 responses to maintain detection accuracy across IP pools.