# Why the Whisper Integration Uses Custom User-Agent Headers in Claude-Video

> Discover why the Whisper integration in claude-video uses custom User-Agent headers to bypass Cloudflare WAF rules and avoid 403 errors, ensuring smooth operation.

- Repository: [bradautomates/claude-video](https://github.com/bradautomates/claude-video)
- Tags: internals
- Published: 2026-08-08

---

**The Whisper integration in `bradautomates/claude-video` injects a custom `User-Agent` header to bypass Cloudflare's Web Application Firewall (WAF) rule 1010, which blocks the default `Python-urllib/3.x` user-agent with a 403 error before authentication occurs.**

The `claude-video` repository implements a *watch* skill that transcribes video audio using external Whisper APIs. When contacting **Groq's** transcription endpoint, the code must present a non-default user-agent string to avoid being blocked by Cloudflare's protective filters. This defensive measure ensures reliable API communication while honestly identifying the client as the watch skill.

## The Cloudflare WAF Blocking Problem

Groq's Whisper endpoint sits behind **Cloudflare's Web Application Firewall (WAF)**, which aggressively filters requests based on user-agent signatures. When Python's standard `urllib` library sends requests, it uses the default header `Python-urllib/3.x`.

Cloudflare's **WAF rule 1010** specifically targets and blocks this default user-agent, returning a **403 Forbidden** response before the request ever reaches Groq's authentication layer. This means even valid API keys cannot authenticate because the connection is terminated at the edge.

The issue is documented in a detailed comment within the source code explaining that any non-default user-agent clears the block, allowing the `Authorization` header to be processed normally.

## How the Custom User-Agent Header Works

The implementation deliberately crafts a recognizable user-agent string that satisfies Cloudflare's requirements while maintaining transparency about the client identity. In [`skills/watch/scripts/whisper.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/whisper.py) at lines 44-51, the `_post_whisper` function constructs headers as follows:

```python
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": f"multipart/form-data; boundary={boundary}",
    # Groq sits behind Cloudflare — the default `Python-urllib/3.x` UA

    # trips WAF rule 1010 (403) before auth even runs. Any non‑default

    # UA clears it; we identify honestly.

    "User-Agent": "watch-skill/1.0 (+claude-code; python-urllib)",
}

```

This header identifies the client as `watch-skill/1.0` with a reference to `claude-code`, distinguishing it from generic Python scripts. By supplying this custom value, the request passes Cloudflare's inspection and proceeds to Groq's API for transcription processing.

## Implementation in the Codebase

The custom user-agent logic resides in the core Whisper client implementation, which handles both Groq and OpenAI endpoints.

### The _post_whisper Function

Located in **[`skills/watch/scripts/whisper.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/whisper.py)**, the `_post_whisper` function builds the multipart form data and injects the critical headers before sending the HTTP request via `urllib`:

```python
def _post_whisper(endpoint: str, api_key: str, model: str, audio_path: Path) -> dict:
    fields = {
        "model": model,
        "response_format": "verbose_json",
        "temperature": "0",
    }
    body, boundary = _build_multipart(fields, audio_path)
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": f"multipart/form-data; boundary={boundary}",
        "User-Agent": "watch-skill/1.0 (+claude-code; python-urllib)",
    }
    # ... request sent via urllib ...

```

The function prepares the audio file for upload alongside parameters specifying the model and output format. The custom `User-Agent` is hardcoded to ensure every request to Groq's Cloudflare-protected endpoint avoids the 403 block.

### Running the Integration

When executing the transcription workflow, the script automatically includes the custom header. Assuming a valid `GROQ_API_KEY` environment variable is configured (as referenced in [`setup.py`](https://github.com/bradautomates/claude-video/blob/main/setup.py)), invoke the skill:

```bash
python3 -m skills.watch.scripts.whisper /path/to/video.mp4

```

The entry point in **[`skills/watch/scripts/watch.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/watch.py)** orchestrates the full pipeline: downloading the video, extracting audio, and calling the Whisper client. Throughout this process, the custom user-agent ensures the Groq API remains accessible without manual intervention.

## Summary

- **Cloudflare WAF rule 1010** blocks the default `Python-urllib/3.x` user-agent with a 403 error before authentication occurs.
- The integration uses the custom header `"watch-skill/1.0 (+claude-code; python-urllib)"` to bypass this restriction.
- The implementation resides in **[`skills/watch/scripts/whisper.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/whisper.py)** within the `_post_whisper` function at lines 44-51.
- This defensive measure applies specifically to the **Groq** endpoint, while maintaining compatibility with OpenAI's Whisper API.
- The header provides honest identification for request tracing while ensuring reliable transcription service access.

## Frequently Asked Questions

### What is Cloudflare WAF rule 1010?

Cloudflare WAF rule 1010 is a security filter that blocks requests originating from generic automation tools and default library user-agents. In the context of the `claude-video` repository, this rule specifically targets the `Python-urllib/3.x` signature used by Python's standard HTTP client, preventing it from reaching Groq's authentication servers.

### Does this affect OpenAI's Whisper API?

No, the custom user-agent primarily addresses Groq's Cloudflare-protected endpoint. While the `_post_whisper` function in [`whisper.py`](https://github.com/bradautomates/claude-video/blob/main/whisper.py) supports both Groq and OpenAI transcription services, the WAF blocking issue is specific to Groq's infrastructure. The custom header is applied universally to maintain consistency, but it is technically required only for the Groq integration.

### What User-Agent string does the integration use?

The integration uses `"watch-skill/1.0 (+claude-code; python-urllib)"`. This string identifies the client as version 1.0 of the watch skill, references the claude-code environment, and acknowledges the underlying Python urllib library. According to the source code comments in [`whisper.py`](https://github.com/bradautomates/claude-video/blob/main/whisper.py), any non-default user-agent would satisfy Cloudflare's requirements, but this specific string provides transparent identification.

### Where is the custom header configured in the codebase?

The custom `User-Agent` header is configured in **[`skills/watch/scripts/whisper.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/whisper.py)** within the `_post_whisper` function at lines 44-51. This function constructs the HTTP headers dictionary that gets passed to the `urllib` request, including the `Authorization`, `Content-Type`, and the custom `User-Agent` required to bypass Cloudflare's WAF rules.