> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwairy.co/llms.txt
> Use this file to discover all available pages before exploring further.

# WordPress

> Install Qwairy's generated PHP tracker in WordPress, protect its key, and verify eligible crawler requests.

Use the WordPress collector when PHP handles the monitored page requests and no managed log-stream connector is selected. The generated tracker reports eligible HTML page requests through the Generic HTTP ingestion endpoint.

<Warning>
  Full-page caches and CDNs can serve a response without running WordPress or PHP. Those requests cannot be observed by this recipe. Use an edge or managed log collector when the traffic you need to measure bypasses PHP.
</Warning>

## Before you start

You need:

* access to **Measure > Crawler Analytics > Settings** for the brand;
* PHP 7.4 or later with the cURL extension enabled;
* access to the active theme directory and `functions.php`;
* outbound HTTPS access from PHP to `https://www.qwairy.co`;
* a server-side secret facility or configuration excluded from source control.

Theme updates can replace custom files. Record the installation or use your normal child-theme deployment process. Only activate one Crawler Analytics delivery source for the brand.

## Create and protect the key

1. In Crawler Analytics settings, select **WordPress**.
2. Select **Create Key**.
3. Enter a descriptive name and the IANA time zone used for daily analytics.
4. Copy the plaintext secret when it appears. Qwairy shows only its prefix later.
5. Store it in server-side configuration. Do not commit it to the theme, expose it to JavaScript, place it in a URL, or write it to logs.

WordPress uses the Generic HTTP provider contract. Its endpoint is `https://www.qwairy.co/api/v1/logs/ingest`.

## Add the generated tracker

Create `qwairy-tracker.php` in the active theme directory with the current generated recipe:

```php theme={null}
<?php
/**
 * Qwairy Crawler Analytics - PHP Tracker
 *
 * WordPress: Add to functions.php:
 *   require_once get_template_directory() . '/qwairy-tracker.php';
 *   qwairy_init('YOUR_API_KEY');
 *
 * Generic PHP: Include at top of index.php:
 *   require_once 'qwairy-tracker.php';
 *   qwairy_init('YOUR_API_KEY');
 */

function qwairy_init($api_key, $options = []) {
    if (empty($api_key)) return;

    $defaults = ['endpoint' => 'https://www.qwairy.co/api/v1/logs/ingest', 'async' => true];
    $options = array_merge($defaults, $options);

    register_shutdown_function(function() use ($api_key, $options) {
        qwairy_send_log($api_key, $options);
    });
}

function qwairy_send_log($api_key, $options) {
    if ($_SERVER['REQUEST_METHOD'] !== 'GET') return;
    $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
    if (!preg_match('/(?:^|[^a-z0-9_-])(?:Claude-SearchBot|MistralAI-Index|OAI-SearchBot|PerplexityBot|ChatGPT-User|Google-GeminiNotebook|Google-NotebookLM|Google-Agent|MistralAI-User|Perplexity-User|Claude-User|Google-CloudVertexBot|ClaudeBot|GPTBot|GrokBot|meta-webindexer|Amzn-User|Meta-ExternalFetcher|AI2Bot|Ai2Bot-Dolma|Amazonbot|Bytespider|CCBot|Meta-ExternalAgent|DuckAssistBot|KimiBot|Kimi-SearchBot|YouBot|Kimi-User|Diffbot|Kangaroo Bot|omgili|omgilibot|PanguBot|Timpibot|Webzio-Extended)(?=$|[^a-z0-9_-])/i', $user_agent)) return;

    $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/';
    if (preg_match('/\.(png|jpe?g|gif|svg|webp|ico|mp4|webm|css|js|json|xml|woff2?|ttf|eot|map)$/i', $path)) return;

    $ignore_paths = ['/wp-admin/', '/wp-json/', '/api/', '/admin/'];
    foreach ($ignore_paths as $ignore) {
        if (strpos($path, $ignore) === 0) return;
    }

    $content_type = '';
    foreach (headers_list() as $header) {
        if (stripos($header, 'content-type:') === 0) {
            $content_type = trim(substr($header, 13));
            break;
        }
    }
    if ($content_type && strpos($content_type, 'text/html') === false) return;

    $log = [
        'status_code' => http_response_code() ?: 200,
        'request_method' => $_SERVER['REQUEST_METHOD'],
        'request_path' => $path,
        'hostname' => $_SERVER['HTTP_HOST'] ?? null,
        'user_agent' => $user_agent,
        'timestamp' => gmdate('c'),
    ];

    if (function_exists('curl_init')) {
        $ch = curl_init($options['endpoint']);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($log),
            CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-API-Key: ' . $api_key],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 5,
        ]);
        curl_exec($ch);
        curl_close($ch);
    }
}
```

Add the setup snippet to `functions.php`:

```php theme={null}
<?php
require_once get_template_directory() . '/qwairy-tracker.php';
qwairy_init('YOUR_API_KEY');
```

If `functions.php` already starts with `<?php`, add only the two statements and do not insert a second opening tag.

`YOUR_API_KEY` is a placeholder. Replace it only during deployment with the server-side value. If your host exposes secrets to PHP as environment variables, keep the value out of theme source:

```php theme={null}
<?php
require_once get_template_directory() . '/qwairy-tracker.php';

$qwairy_api_key = getenv('QWAIRY_API_KEY');
if (is_string($qwairy_api_key) && $qwairy_api_key !== '') {
    qwairy_init($qwairy_api_key);
}
```

Do not initialize the tracker twice. The generated function names are global, so confirm that another plugin or theme file does not define `qwairy_init` or `qwairy_send_log`.

## Verify delivery

Use an uncached HTML page and replace the example hostname. The tracker accepts `GET`, not `HEAD`. This synthetic request creates a crawler observation in the current analytic day:

```bash theme={null}
curl --user-agent "Mozilla/5.0 (compatible; GPTBot/1.1)" \
  --output /dev/null \
  --write-out "%{http_code}\n" \
  "https://www.example.com/docs/qwairy-collector-check"
```

1. Confirm the request returns the expected page status.
2. Confirm PHP handled the request rather than a CDN or full-page cache.
3. In Qwairy, wait for the connector to move from pending to connected after the accepted event reaches a daily rollup.
4. Open the current analytic day and look for `/docs/qwairy-collector-check`.

If the page loads but the connector stays pending, verify the cURL extension, outbound HTTPS, key, hostname, User-Agent, current timestamp, content type, and ignored-path rules.

## Limits and behavior

* The generated tracker reports only `GET` requests with a matching User-Agent.
* Static file extensions and paths beginning with `/wp-admin/`, `/wp-json/`, `/api/`, or `/admin/` are ignored.
* A response with an explicit content type is reported only when it contains `text/html`.
* Delivery is a blocking cURL call during PHP shutdown with a five-second timeout. The generated tracker has no retry queue and does not inspect the Qwairy response.
* The integration route allows a burst of 120 ingestion requests per minute. Responses above that rate are not retried by the generated tracker.
* The endpoint applies hostname validation, Qwairy exclusions, maintained crawler classification, a 72-hour late-arrival window, and shared ingestion ceilings.
* The shared technical ceilings are 10,000,000 events per integration per day and 200,000,000 events per billed team per day.
* The collector observes requests. It does not prove indexing, model training, or citation use.

## Rotate or roll back

To rotate the key, create a replacement in Qwairy, update the server-side value, confirm a new accepted observation, and then delete the previous key.

To roll back the tracker:

1. Remove the `require_once` and `qwairy_init` call from `functions.php`.
2. Deploy and clear the relevant WordPress or host caches.
3. Confirm the site still serves normally and no new tracker observations arrive.
4. Remove `qwairy-tracker.php` and the server-side secret.
5. Revoke the corresponding Qwairy key.

Disabling delivery creates an analytics coverage gap. Check delivery-continuity warnings before comparing periods that cross the rollback.

## Related pages

* [Crawler Analytics](/documentation/measure/crawler-analytics)
* [Generic HTTP collector](/documentation/measure/crawler-analytics/connectors/generic-http)
