# How the Holehe Register Check Module Detects Email Usage Across Websites

> Learn how Holehe's Register Check module detects email usage across websites by sending validation requests and interpreting error codes, without creating accounts.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: internals
- Published: 2026-08-24

---

**Holehe’s Register Check module determines if an email address is already registered on a service by sending non-intrusive registration validation requests and interpreting specific error codes that indicate account existence, all without creating actual accounts.**

The open-source OSINT tool Holehe (maintained by megadose/holehe) identifies where an email address appears online by leveraging a specialized **Register Check** system. This mechanism uses dynamically loaded, site-specific modules to probe registration validation endpoints. Each module follows a standardized three-step pattern to distinguish between "account exists," "available," and "rate-limited" states through careful analysis of API responses.

## Core Architecture and Module Discovery

The Register Check system relies on dynamic module loading and a centralized dispatcher to manage concurrent execution across hundreds of supported websites.

### Dynamic Module Loading via `import_submodules`

Holehe discovers available site checks through automatic package introspection. In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 37-47), the `import_submodules()` function walks the `holehe/modules/*` directory tree, imports every Python file, and exposes their public functions. The `get_functions()` helper then filters these imports to extract only async functions representing individual site checks.

Each Register Check module must define three constant fields used by the core logic:

```python
name    = "tumblr"      # Human-readable identifier

domain  = "tumblr.com"  # Displayed domain

method  = "register"    # Flags this as a register-check module

```

Only modules with `method == "register"` are selected for execution when running a Register Check workflow.

### The Launch Dispatcher

The `launch_module()` function in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 66-73) serves as the execution wrapper. It receives the site-specific async function, the target email, an `httpx.AsyncClient` instance, and a shared results list. This dispatcher executes the module within a try-catch block, converting unexpected exceptions into generic rate-limit entries to prevent individual module failures from crashing the entire scan.

## The Three-Step Register Check Implementation Pattern

Every Register Check module follows an identical operational pattern to test for account existence without completing actual registration.

### Step 1: Token and Session Acquisition

Modules begin by preparing the HTTP session. This typically involves a `GET` request to the site's registration page to obtain anti-CSRF tokens, session cookies, or bearer tokens required for subsequent API calls.

### Step 2: Registration Payload Submission

The module constructs and sends a `POST` request to the service’s registration validation endpoint. The payload includes the target email address paired with a dummy password and random username. This mimics a legitimate registration attempt while providing the necessary data to trigger validation logic on the server.

### Step 3: Response Interpretation and Error Mapping

The critical logic resides in parsing the API response. Services return specific error codes or messages when an email already exists in their database:

- **Account exists**: Specific error indicators (e.g., Tumblr error code `2`, Xing message *"We already know this e-mail address."*) map to `exists: True`
- **Available/Invalid**: Password policy failures or validation errors map to `exists: False`
- **Rate limits**: HTTP 429 responses or network failures map to `rateLimit: True`

## Anatomy of a Register Check Module: The Tumblr Example

The Tumblr implementation in [`holehe/modules/social_media/tumblr.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/tumblr.py) demonstrates the complete workflow. The module extracts a bearer token and CSRF header, submits a validation payload, and interprets the JSON response:

```python
async def tumblr(email, client, out):
    # Constants required by core.py

    name = "tumblr"
    domain = "tumblr.com"
    method = "register"
    
    # Step 1: Obtain authentication tokens

    getBearer = await client.get("https://www.tumblr.com/", headers={...})
    bearer = f"Bearer {extract_token(getBearer)}"
    csrf = getBearer.headers["X-Csrf"]
    
    # Step 2: Submit validation payload

    data = json.dumps({
        "password": random_string(),
        "email": email,
        "tumblelog": random_string()
    })
    
    post = await client.post(
        "https://www.tumblr.com/api/v2/register/account/validate",
        headers={"Authorization": bearer, "X-CSRF": csrf, ...},
        data=data
    )
    
    # Step 3: Interpret error codes

    error = post.json()["response"]["code"]
    if error == 2:  # "User already exists"

        out.append({"name": name, "domain": domain, "method": method,
                    "exists": True, "rateLimit": False})
    elif error == 1030:  # Password too short

        out.append({"name": name, "domain": domain, "method": method,
                    "exists": False, "rateLimit": False})
    else:
        raise Exception  # Triggers rate-limit handling in core

```

This skeleton appears consistently across modules like [`xing.py`](https://github.com/megadose/holehe/blob/main/xing.py), [`github.py`](https://github.com/megadose/holehe/blob/main/github.py), and [`lastpass.py`](https://github.com/megadose/holehe/blob/main/lastpass.py), varying only in endpoint URLs, required headers, and specific error signatures.

## Concurrent Execution and Result Aggregation

When invoked via the CLI (`holehe <email>`), the core orchestration logic performs four distinct operations:

1. **Loads all modules** using `import_submodules()` to discover available checks
2. **Filters by method**, selecting only those with `method == "register"`
3. **Executes concurrently** using a Trio nursery, running `launch_module` for each site in parallel
4. **Aggregates results**, sorting them into a color-coded table:
   - **[+]** – Email exists (confirmed via register-check validation)
   - **[-]** – Email not found
   - **[x]** – Rate limit or temporary block encountered

## Summary

- **Dynamic discovery**: Holehe automatically loads Register Check modules from `holehe/modules/*` using `import_submodules()` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)
- **Standardized interface**: Every module exposes `name`, `domain`, and `method = "register"` constants for identification
- **Non-intrusive validation**: The Register Check sends dummy registration data to validation endpoints without creating accounts
- **Error-code analysis**: Modules parse specific API error responses to distinguish existing accounts (e.g., Tumblr error `2`) from available emails
- **Concurrent execution**: The `launch_module` dispatcher manages parallel execution via Trio, handling exceptions as rate-limits to ensure stability

## Frequently Asked Questions

### How does Holehe avoid creating actual accounts during the Register Check?

Holehe exploits the validation phase of registration workflows rather than completion endpoints. By sending registration data to `validate` or `check` endpoints (such as Tumblr’s `/api/v2/register/account/validate`), the tool triggers the server-side email lookup without submitting the final account creation request. The service returns error codes indicating whether the email exists, allowing Holehe to determine registration status without completing sign-up.

### What differentiates a Register Check module from other Holehe modules?

Register Check modules specifically set `method = "register"` and target registration validation endpoints. Unlike recovery-based or login-based checks, these modules intentionally submit malformed or dummy registration payloads (random passwords, invalid usernames) to force validation errors. This contrasts with password reset flows or OAuth checks used by other module types in the Holehe ecosystem.

### How can I add a custom Register Check module for a new service?

Create a Python file in `holehe/modules/` (e.g., [`holehe/modules/social_media/myservice.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/myservice.py)) containing an async function that accepts `(email, client, out)` parameters. Define the required constants (`name`, `domain`, `method = "register"`), implement the three-step pattern (token acquisition, validation POST, error interpretation), and append a dictionary with `exists` and `rateLimit` booleans to the `out` list. Holehe automatically discovers the module on next execution via `import_submodules()`.

### Why does Holehe mark some results as rate-limited [x] instead of exists [+] or not exists [-]?

When a module encounters HTTP 429 responses, CAPTCHA challenges, or unhandled exceptions (network timeouts, JSON parsing errors), the `launch_module` wrapper in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) catches these and marks the result as rate-limited. This distinguishes temporary blocks from definitive account existence checks, ensuring the user knows when to retry rather than accepting a false negative.