Node, Bun & Deno
Use the official @zeroad.network/token package to announce your Publisher ID and recognise subscribers. It works with any HTTP framework.
In short
- You need: Node, Bun or Deno, your Publisher ID, and the list of hostnames you serve.
- You do: install the package, add one middleware, and branch on
isZeroAdSubscriber. - Done when: a test subscriber gets the clean page, and everyone else gets your normal one.
1. Install
npm install @zeroad.network/token
With Bun, run bun add @zeroad.network/token. With Deno, run deno add npm:@zeroad.network/token.
2. Create the publisher once
Create it at startup, and reuse it for the life of the process.
This is an example Publisher ID. Replace it with your own before using it.
import { createPublisher } from "@zeroad.network/token"
export const publisher = createPublisher({
publisherId: "zapub_7Fq2xR9nKd3wV8mB4tL6yH1c",
hostnames: "example.com", // or ["example.com", "blog.example.com"]
})
List every hostname you serve in hostnames. A token only verifies on a hostname in this list. Listing example.com also allows www.example.com, and the other way round.
3. Check every request
Add one middleware. It sends your header, checks the visitor, and stores the answer as isZeroAdSubscriber.
Hono
app.use(async (c, next) => {
c.header(...publisher.header)
const visitor = await publisher.verify(
c.req.header(publisher.tokenHeaderName),
c.req.header("host"),
)
c.set("isZeroAdSubscriber", visitor.subscriber)
await next()
})
Express
app.use(async (req, res, next) => {
res.setHeader(...publisher.header)
const visitor = await publisher.verify(
req.headers[publisher.tokenHeaderNameLowercase],
req.headers.host,
)
res.locals.isZeroAdSubscriber = visitor.subscriber
next()
})
verify() never throws on a bad token. A missing, broken, expired or wrong-site token simply returns subscriber: false.
4. Serve subscribers the clean page
Where you render ads, trackers, cookie banners and popups, skip them for subscribers:
if (!isZeroAdSubscriber) {
// Render ads, cookie consent, non-essential trackers and marketing popups.
}
If you sell access, unlock your base subscription or an included level of paid content for subscribers. Higher tiers can stay restricted. Check the requested content against your included level before rendering it.
5. Keep subscriber pages out of shared caches
The SDK caches verification results, not your pages. If a CDN, proxy or page cache sits in front of your app, set it to bypass the cache for requests that carry Better-Web-Token, so they always reach your app.
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 caches warm. You should see the normal page.
Troubleshooting
Log visitor.reason to see why a token was rejected, and visitor.hostname to see which hostname was checked.
reason |
What it usually means |
|---|---|
missing |
No token arrived. Most visitors are not subscribers. For a test subscriber, check that your proxy forwards Better-Web-Token, and reload after the first visit. |
unknown_hostname |
The hostname isn't in hostnames. Add it to your configuration. |
wrong_hostname |
The token was made for a different hostname. Check that your proxy keeps the public Host header. |
expired |
The token is out of date. The extension refreshes tokens on its own. |
unsupported_version |
The token is newer than your SDK. Check for an SDK update. |
Good to know
- Pass the real hostname.
example.comandwww.example.comhave different signatures. If you configure several hostnames, you must pass one toverify(). - Results are cached by default, so returning subscribers don't repeat the cryptography. Pass
cache: falseto turn it off. - Already-clean website? You don't need
verify(). Sending the header, withresponse.setHeader(...publisher.header), is enough to earn.