# How to Add New Website Support to holehe: A Complete Developer Guide

> Learn how to add new website support to holehe with this developer guide. Follow our easy steps to create a Python function and integrate new modules.

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

---

**Create a single async Python function in `holehe/modules/` that accepts `email`, `client`, and `out` parameters, following the signature pattern used by existing modules like Facebook and Twitter.**

holehe is a modular email reconnaissance tool developed by megadose that checks whether an email address exists across hundreds of web services. Adding new website support requires understanding its module-based architecture, where each service is implemented as a standalone async function discovered automatically at runtime. This guide walks through the exact steps to extend holehe's coverage, referencing the actual source code patterns from the repository.

## How holehe's Module System Works

The core discovery engine lives in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py). At startup, it uses `importlib` to iterate over all Python files under `holehe/modules/`, treating each as a potential service check. No manual registration is required—any valid module following the naming convention is automatically loaded and executed concurrently.

Each module exposes exactly one **async function** whose name matches the service. The function signature is strictly enforced:

```python
async def <service_name>(email: str, client: httpx.AsyncClient, out: list) -> None:

```

| Parameter | Purpose |
|-----------|---------|
| `email` | The target email address being tested |
| `client` | Shared `httpx.AsyncClient` for connection reuse across all modules |
| `out` | Result list to which modules append outcome dictionaries |

The concurrent execution model means modules run in parallel, so your implementation must be fully async—no blocking I/O.

## Required Module Structure

Every module must follow the pattern demonstrated in [`holehe/modules/social_media/facebook.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/facebook.py):

### Static Metadata Fields

Define these four variables at the function's start:

- `name` — Human-readable service name
- `domain` — Primary domain (used for output grouping)
- `method` — Check strategy: `"register"`, `"password_reset"`, or `"login"`
- `frequent_rate_limit` — Boolean flag indicating aggressive rate-limiting

### Request Flow Pattern

Analyze existing modules to identify the typical implementation path:

1. **Pre-flight (optional)** — Fetch CSRF tokens, cookies, or session data via `GET`
2. **Primary request** — Send `POST` to registration/password endpoint with target email
3. **Response analysis** — Parse JSON to determine: account exists, doesn't exist, or rate-limited
4. **Result recording** — Append standardized dictionary to `out`

The result dictionary requires six specific keys:

| Key | Value Type | Description |
|-----|-----------|-------------|
| `name` | `str` | Service name (matches `name` variable) |
| `domain` | `str` | Primary domain |
| `method` | `str` | Check method used |
| `frequent_rate_limit` | `bool` | Rate-limit flag |
| `rateLimit` | `bool` | Whether this specific check hit rate limiting |
| `exists` | `bool` | Whether the email has an existing account |
| `emailrecovery` | `str` \| `None` | Recovered partial email (if leak reveals it) |
| `phoneNumber` | `str` \| `None` | Recovered phone number (if leak reveals it) |
| `others` | `dict` \| `None` | Additional extracted metadata |

## Step-by-Step Implementation Walkthrough

### Step 1: Create the Module File

Place your file under the appropriate category subdirectory:

```

holehe/modules/
├── social_media/
├── shopping/
├── adult/
├── entertainment/
├── music/
├── transport/
└── [your_category]/
    └── yourservice.py

```

Create the file as [`holehe/modules/social_media/examplesite.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/examplesite.py) for this walkthrough.

### Step 2: Import Dependencies

Every module needs `random` and `string` for payload generation, plus the user-agent pool:

```python

# holehe/modules/social_media/examplesite.py

import random
import string
from holehe.localuseragent import ua

```

The `ua` dictionary in [`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py) provides realistic browser strings to avoid detection.

### Step 3: Define the Async Function

Name the function exactly as the file (without `.py`):

```python
async def examplesite(email, client, out):
    name = "ExampleSite"
    domain = "examplesite.com"
    method = "register"
    frequent_rate_limit = False

```

The parameter names `email`, `client`, and `out` must match exactly—[`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) passes these positionally.

### Step 4: Configure Request Headers

Mimic legitimate browser traffic:

```python
    headers = {
        "User-Agent": random.choice(ua["browsers"]["chrome"]),
        "Accept": "application/json, text/plain, */*",
        "Origin": "https://www.examplesite.com",
        "DNT": "1",
    }

```

### Step 5: Handle Pre-flight Requirements

Many services require tokens or cookies before the main request. Wrap in try/except to remain resilient:

```python
    # Optional: fetch CSRF token

    try:
        r = await client.get("https://www.examplesite.com/signup", headers=headers)
        token = r.text.split('name="csrf_token" value="')[1].split('"')[0]
    except Exception:
        token = None

```

Use the shared `client` parameter—never create your own `httpx.AsyncClient`.

### Step 6: Build and Send the Check Request

Construct payloads that trigger email validation without completing registration:

```python
    payload = {
        "email": email,
        "username": "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(12)),
        "password": "DummyPass123!",
    }
    if token:
        headers["X-CSRFToken"] = token

    try:
        resp = await client.post(
            "https://api.examplesite.com/v1/users",
            data=payload,
            headers=headers
        )
        data = resp.json()

```

### Step 7: Interpret the Response

Service responses vary—study the target's API behavior to identify existence indicators:

```python
        if data.get("error") == "email_already_used":
            exists = True
        else:
            exists = False
        rate_limited = data.get("error") == "rate_limit_exceeded"
    except Exception:
        exists = False
        rate_limited = True

```

Common existence signals include: `"email taken"`, `"account exists"`, specific error codes, or redirect to login versus signup flow.

### Step 8: Record the Result

Always append to `out` with the complete dictionary structure:

```python
    out.append({
        "name": name,
        "domain": domain,
        "method": method,
        "frequent_rate_limit": frequent_rate_limit,
        "rateLimit": rate_limited,
        "exists": exists,
        "emailrecovery": None,
        "phoneNumber": None,
        "others": None,
    })

```

## Complete Working Example

Here's the full [`examplesite.py`](https://github.com/megadose/holehe/blob/main/examplesite.py) module combining all steps:

```python

# holehe/modules/social_media/examplesite.py

import random
import string
from holehe.localuseragent import ua


async def examplesite(email, client, out):
    name = "ExampleSite"
    domain = "examplesite.com"
    method = "register"
    frequent_rate_limit = False

    headers = {
        "User-Agent": random.choice(ua["browsers"]["chrome"]),
        "Accept": "application/json, text/plain, */*",
        "Origin": "https://www.examplesite.com",
        "DNT": "1",
    }

    # Optional: fetch CSRF token

    try:
        r = await client.get("https://www.examplesite.com/signup", headers=headers)
        token = r.text.split('name="csrf_token" value="')[1].split('"')[0]
    except Exception:
        token = None

    # Build registration payload

    payload = {
        "email": email,
        "username": "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(12)),
        "password": "DummyPass123!",
    }
    if token:
        headers["X-CSRFToken"] = token

    # Send request and evaluate response

    try:
        resp = await client.post(
            "https://api.examplesite.com/v1/users",
            data=payload,
            headers=headers
        )
        data = resp.json()
        if data.get("error") == "email_already_used":
            exists = True
        else:
            exists = False
        rate_limited = data.get("error") == "rate_limit_exceeded"
    except Exception:
        exists = False
        rate_limited = True

    # Store outcome

    out.append({
        "name": name,
        "domain": domain,
        "method": method,
        "frequent_rate_limit": frequent_rate_limit,
        "rateLimit": rate_limited,
        "exists": exists,
        "emailrecovery": None,
        "phoneNumber": None,
        "others": None,
    })

```

## Testing Your New Module

After saving the file, verify integration immediately:

```bash

# From repository root

python -m holehe -e test@example.com | grep -i examplesite

```

No reinstallation or restart is required—[`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) discovers modules dynamically on each invocation.

## Key Reference Files

| File | Purpose |
|------|---------|
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | Module discovery, async orchestration, and result aggregation |
| [`holehe/modules/__init__.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/__init__.py) | Package marker enabling dynamic imports |
| [`holehe/modules/social_media/facebook.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/facebook.py) | Production reference for CSRF handling and response parsing |
| [`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py) | Browser user-agent strings for request mimicry |
| [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py) | CLI formatting utilities |

## Summary

- **Module location**: Create `.py` files under `holehe/modules/<category>/`
- **Function signature**: `async def servicename(email, client, out)` — exact parameter names required
- **Automatic discovery**: No registration needed; `importlib` finds modules at runtime
- **Result contract**: 9-key dictionary with `exists`, `rateLimit`, and metadata fields
- **Shared resources**: Use the provided `client` for all HTTP operations; import `ua` for user-agents
- **Error resilience**: Wrap external requests in try/except; assume network failures mean rate-limiting

## Frequently Asked Questions

### Do I need to modify any configuration files to register a new module?

No. The dynamic loader in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) uses `importlib` to scan all subdirectories under `holehe/modules/` at runtime. Simply placing a valid Python file with the correct function signature is sufficient—no [`__init__.py`](https://github.com/megadose/holehe/blob/main/__init__.py) imports or registry updates required.

### Can I use synchronous HTTP libraries like `requests` in my module?

No. holehe executes all modules concurrently using `asyncio.gather()`. Blocking calls freeze the entire event loop and degrade performance for other checks. Always use `await client.get()` or `await client.post()` with the shared `httpx.AsyncClient` instance.

### How do I handle services that require JavaScript or browser automation?

holehe modules are HTTP-only. Services requiring JavaScript execution (React apps, heavy obfuscation) are typically unsupported—consider tools like Playwright or Selenium in separate projects. For APIs protected by basic obfuscation, study existing modules like [`facebook.py`](https://github.com/megadose/holehe/blob/main/facebook.py) which handle CSRF tokens and cookie sessions through raw HTTP.

### What should I set for `frequent_rate_limit` versus `rateLimit`?

`frequent_rate_limit` is static metadata warning users that this service aggressively limits requests. Set it to `True` based on the platform's known reputation. `rateLimit` is dynamic—set it to `True` when your specific request receives a rate-limit response. Both appear in CLI output to inform users of reliability.