PHP

Use the zeroad.network/token Composer package to announce your Publisher ID and recognise subscribers. It works with plain PHP, Laravel, Symfony and other frameworks.

In short

  • You need: PHP 7.2 or newer with the Sodium extension (ext-sodium), and your Publisher ID.
  • You do: install the package, check each request, and branch on $isZeroAdSubscriber.
  • Done when: a test subscriber gets the clean page, and everyone else gets your normal one.

Using WordPress? Use the WordPress plugin instead.

1. Install

composer require zeroad.network/token

2. Create the publisher

Create it in your application bootstrap or service container.

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. Use an array for several domains. Listing example.com also allows www.example.com.
  • Keep the list in your configuration. Don't build it from the incoming request.
  • Reuse the instance across requests in a long-running app. With PHP-FPM, bootstrap runs on each request anyway.

3. Check every request

Send your header before any output. Then pass the token and the request's hostname to verify():

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

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

$isZeroAdSubscriber = $visitor->subscriber;

$visitor->subscriber is true only for a verified subscriber. A missing, broken, expired or wrong-site token returns false. Having a token header doesn't make someone a subscriber.

PHP exposes the Better-Web-Token header as $_SERVER['HTTP_BETTER_WEB_TOKEN'].

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

4. Serve subscribers the clean page

Wrap ads, cookie consent, non-essential trackers and marketing popups in the same condition:

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

If you sell access, unlock your base subscription or an included level of paid content for subscribers. Higher tiers can stay restricted.

Unlock it on the server. Hiding a paywall overlay doesn't help if your server never sent the content.

5. Keep subscriber pages out of shared caches

The SDK's cache stores verification results, not your pages. If a CDN, reverse proxy or page cache sits in front of PHP, set it to bypass the cache for requests that carry Better-Web-Token. Those requests must reach PHP.

Set up page caching and CDNs →

6. Check it works

  1. Confirm a normal page response includes Better-Web-Publisher.
  2. Open your website's page in your dashboard, and select Test in your browser.
  3. Reload your website. You should see the clean page and your included content.
  4. Open the same URL in a browser without the extension, with your page cache on. You should see the normal page.

Troubleshooting

A test subscriber isn't recognised. Check $visitor->reason for why, and $visitor->hostname for the hostname that was checked. Then check that:

  • your proxy forwards Better-Web-Token to PHP
  • your proxy keeps the public hostname
  • the hostname is in your hostnames list

The very first visit may arrive without a token. Reload the page.

Verification caching

'cache' => ['store' => 'auto'] shares verification results through APCu when it's available. Otherwise, they're kept in memory on the publisher instance. With PHP-FPM, that memory ends with each request.

APCu is optional. Verification works without it.