Getting started
Ten minutes, four steps. At the end you'll have a running ledger of every agent that crossed your app, attributed to a cryptographic identity, and exportable as something you can invoice from.
1 Install the middleware
Zero dependencies. It uses Node's own crypto and pulls nothing into your tree.
npm install wayleave
Requires Node 18 or newer.
2 Get an API key
Go to Account → Issue key. Give it a
label like production. The key is shown once and never
again — we store only a hash of it, so if you lose it you issue a new one
rather than recovering the old.
Give staging its own key. If one leaks you can revoke it without stopping your production billing.
3 Start in observe-only mode
Do not price anything yet. Run it for a few days with no prices and no deny rules, and look at what actually crosses your app. Almost everyone is surprised, and you cannot price sensibly until you know what is out there.
import Wayleave from 'wayleave';
import { MeterSink } from 'wayleave/meter';
const sink = new MeterSink({
endpoint: 'https://meter.wayleave.dev',
apiKey: process.env.WAYLEAVE_METER_KEY,
onError: (err, info) => console.warn('[wayleave]', err.message, info),
});
const gate = new Wayleave({
pricedPaths: {}, // observe only — nothing is charged, nothing is blocked
rules: {}, // no deny rules — measure first, police later
sink,
});
app.use(gate.express()); // mount BEFORE your auth middleware
process.on('SIGTERM', () => sink.close()); // drain on shutdown
Mount it before authentication. After your auth layer you only see traffic that already had a key, which is exactly the traffic you do not need to measure.
Always call sink.close() on shutdown.
Without it, whatever is buffered when your container cycles is data you
never receive.
curl -i -A 'GPTBot/1.0' https://your-app.com/health # expect 200
If this integration ever returns 402 or 403,
it is misconfigured. In observe-only mode nothing should ever be refused.
4 Read your ledger
Within a minute, crossings appear on your ledger. Four numbers matter:
- Unbilled agent crossings
- Agents that crossed and produced no revenue. This is the big number, and it is the point: it is what you are currently giving away.
- Turned away at a 402
- Agents that asked for a priced route and never paid. Most crawlers have no wallet today, so they are refused rather than served free. You are protected, not paid — and the ledger shows exactly how much demand is standing at your gate.
- Collectable
- Money a payment rail actually confirmed. Invoice this number and no other. It will be small at first. That is honest, not broken.
- Unconfirmed
-
Your middleware booked it as billed, but no rail confirmed it. Usually
settlement lag. If it stays high, your
verifyPaymentis trusting something it should not — do not invoice it.
Only verified_agent rows are marked invoiceable. A
self-declared bot is an identity you cannot defend on an invoice, so we
show it as observed only.
5 When you're ready, charge
Two changes. Both matter.
const gate = new Wayleave({
pricedPaths: { '/api/premium': 0.05 }, // 5¢ per crossing
// REQUIRED on anything priced. Without it, a bot that sends a browser
// user-agent and an accept-language header is classified as human and
// crosses free — that is two headers, and it is the whole bypass.
strictPricedPaths: true,
confirmHuman: req => Boolean(req.session?.userId), // your session, never a header
// REQUIRED to sell anything. Defaults to deny: configure nothing and
// priced routes stay 402 forever, deliberately.
verifyPayment: (proof, ctx) => facilitator.confirmSettled(proof, ctx),
sink,
});
Set your receiving address too. Agents pay a facilitator and the facilitator pays that address. Wayleave never holds your funds and is never a step in the path — we record that money moved, we do not move it.
What this does not do
Worth knowing before you rely on it.
- It is a tollbooth, not a wall. Only the verified lane is cryptographic. A bot willing to lie about being a browser gets through, and there is no TLS fingerprinting here — that belongs at your CDN. Wayleave prices disclosure; it does not detect concealment.
- It works because good operators want to be identified. Signed traffic gets allowlisted instead of blocked. That is the bet.
- Key rotation is your job for now. You can pass a resolver function for keys, but there is no JWKS fetcher in the box.
-
Rate-limit and replay state is per-process unless you
supply a shared
store. Two instances mean two independent limiters.
If something looks wrong
- No crossings appear
-
Check the key is right (
401in your logs viaonError), that you mounted before auth, and that at least one flush interval has passed. The sink batches; it is not instant. - Numbers look doubled
- They should not be. Every event carries an idempotency key and the meter refuses repeats, so retries are safe — send the same batch as many times as you like. If you genuinely see doubles, tell us; that is a bug in us, not in you.
- Collectable is zero but agents are paying
-
You have no receiving address set, or your
verifyPaymentis not wired to a real facilitator. We only count settlement a rail confirmed. - Real users are getting 402
-
You turned on
strictPricedPathswithout a workingconfirmHuman. It fails closed on purpose. Point it at your session, never at a header.