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.comalso allowswww.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
- Confirm a normal page response includes
Better-Web-Publisher. - Open your website's page in your dashboard, and select Test in your browser.
- Reload your website. You should see the clean page and your included content.
- 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-Tokento PHP - your proxy keeps the public hostname
- the hostname is in your
hostnameslist
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.