PHP

The zeroad.network/token Composer package announces your Publisher ID and verifies subscriber tokens locally. It works with plain PHP and frameworks such as Laravel and Symfony. For WordPress, use the WordPress plugin.

Before you start

You need PHP 7.2+ or PHP 8, the Sodium extension (ext-sodium), and your Publisher ID from the dashboard. The ID starts with zapub_ and belongs to your publisher account. You don't register each site separately: accepted subscriber activity can create it after upload and processing. You can also add and verify it from your dashboard.

Install

composer require zeroad.network/token

Create the publisher

Load Composer's autoloader and create the publisher in your application bootstrap or service container. Replace the example ID and hostname with your own values.

This is an example Publisher ID. Replace it with your own before using it.

<?php

require_once __DIR__ . '/vendor/autoload.php';

use ZeroAd\Token\Publisher;

$publisher = Publisher::create([
    'publisherId' => 'zapub_7Fq2xR9nKd3wV8mB4tL6yH1c',
    'hostnames' => 'example.com',
    'cache' => ['store' => 'auto'],
]);

List every hostname you serve, using an array for multiple domains. The SDK accepts the www counterpart of each configured hostname too. Keep this list in your configuration; don't build it from the incoming request.

In PHP-FPM, bootstrap runs for each request. In a long-running application, reuse the publisher instance across requests.

Verify each request

Send the publisher header before any output, then pass the token and request hostname to verify():

header("{$publisher->headerName}: {$publisher->headerValue}");

$visitor = $publisher->verify(
    $_SERVER[$publisher->tokenHeaderServerKey] ?? null,
    $_SERVER['HTTP_HOST'] ?? ''
);

$isZeroAdSubscriber = $visitor->subscriber;

The response announces Better-Web-Publisher. The extension sends subscribers' tokens in Better-Web-Token, which PHP exposes as HTTP_BETTER_WEB_TOKEN.

verify() returns a result object. Use $visitor->subscriber, a boolean stored here as $isZeroAdSubscriber, to decide which page to render for a verified Zero Ad Network subscriber. A missing, invalid, expired, or wrong-host token returns false. Merely having a token header does not make someone a subscriber.

In framework middleware, read the same headers through the request object, pass $visitor to your templates, and set the publisher header on the response object.

Render the subscriber experience

For a verified subscriber, skip ads, non-essential third-party trackers, cookie consent screens, and marketing popups, including newsletter signup prompts. Grant your base subscription or a custom level that unlocks paid content or functionality; higher tiers can remain restricted. For other visitors, keep your normal rendering and access rules.

<?php if (!$isZeroAdSubscriber): ?>
    <aside class="advertisement">Your normal ad markup</aside>
<?php endif; ?>

Apply the same condition where you include tracking scripts and select paywalled content. Removing an overlay alone does not unlock content that your server never sent.

Caching

The example uses cache: ['store' => 'auto']: verification results are shared through APCu when it is available, otherwise cached in memory on the publisher instance. On ordinary PHP-FPM requests, that memory cache ends with the request. APCu is optional; verification works without it.

This is a token verification cache, separate from your page cache. Configure your CDN, reverse proxy, and application page cache to bypass both cache reads and writes for requests carrying Better-Web-Token. The request must reach PHP for verification, and the subscriber response must not become a shared page for other visitors.

Check the integration

  1. Confirm a normal page response includes Better-Web-Publisher.
  2. On your site's dashboard page, use Test in your browser with the extension installed.
  3. Check that the subscriber sees the clean page and unlocked content.
  4. Open the same URL in a browser without the extension and confirm the regular experience still works, including with your page cache enabled.

If a subscriber is not recognised, check that your proxy forwards Better-Web-Token and preserves the public hostname. $visitor->reason explains a rejection; $visitor->hostname shows which hostname was checked.