Verifiable counts

A reaction count on your page is a number your visitors have to take on faith. Tapback gives you two ways to back it up: a people number that costs far more to fake than raw taps, and a signed count anyone can verify against Tapback's public key without trusting your site.

Read counts over a public API

Every page's counts are readable with your publishable key — no secret, no auth, cached at the edge:

curl "https://api.tapback.party/v1/counts?key=pk_live_...&entity=/blog/my-post"
{ "counts":  { "heart": 18234, "idea": 512 },
  "uniques": { "heart": 4112,  "idea": 340 },
  "seq": 91 }

counts is total taps per reaction. uniques is how many distinct people reacted — the number behind "4,112 people loved this". The response is cached for up to 30 seconds; the live socket (via the JS API) is the real-time path.

Why uniques are harder to inflate

Taps are cheap to pile up — one enthusiastic reader (or one script) can add up to the per-person cap (50 on the free plan) to a reaction. A person is expensive: Tapback admits at most 10 new reactors per IP per page per day, so every fake person burns one of an address's ten daily slots — a thousand fake uniques cost at least a hundred IP-days, and the same hundred IP-days buy fifty thousand taps. When you want a number that stands for real reach, it's uniques.

The uniques counter only ever goes up. It's incremented once, the first time a given reader reacts to a given reaction, and it stays put when that reader's local id later expires or is erased — so it counts distinct people over the life of the page, and it survives a quiet page going to sleep and waking again. Read how anonymity works for what a "reader" is and why the same person can occasionally re-count.

Signed attestations

A screenshot proves nothing — anyone can edit one. An attestation is a statement, signed by Tapback's key, of what the counts were at a moment in time. You (or a partner, an auditor, an archive) can verify it offline against Tapback's published key.

Issuing attestations is a Pro plan feature. Verifying needs no plan at all — the key set is public, and an attestation issued while a Tapbar was on Pro stays verifiable forever, even if the plan lapses:

curl "https://api.tapback.party/v1/attest?key=pk_live_...&entity=/blog/my-post"
{ "payload": "eyJ2IjoxLCJhcHAiOiJhcHBfLi4uIiwiZW50aXR5Ijoi...",
  "kid": "att-2026-07",
  "sig": "9f8a2c..." }

payload is a base64url-encoded JSON statement; sig is an Ed25519 signature over it. Decoded, the payload reads:

{ "v": 1,
  "app": "app_9f2c",
  "entity": "/blog/my-post",
  "at": 1789300000,
  "counts": { "heart": { "total": 18234, "uniques": 4112 } } }

at is the Unix second the counts were observed. The signature covers the whole payload, so changing any number breaks it.

Verify one

Tapback publishes its public keys as a JWK set. Verification is a few lines of standard WebCrypto — the same in a browser, a Worker, or Node:

const b64url = (s) => Uint8Array.from(atob(s.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));

const att = await (await fetch("https://api.tapback.party/v1/attest?key=pk_live_...&entity=/blog/my-post")).json();
const { keys } = await (await fetch("https://api.tapback.party/.well-known/tapback-attestation.json")).json();

const jwk = keys.find((k) => k.kid === att.kid);
const key = await crypto.subtle.importKey("jwk", jwk, { name: "Ed25519" }, false, ["verify"]);
const ok = await crypto.subtle.verify("Ed25519", key, b64url(att.sig), new TextEncoder().encode(att.payload));

if (ok) {
  const claim = JSON.parse(new TextDecoder().decode(b64url(att.payload)));
  console.log(`${claim.counts.heart.uniques} people, verified as of ${new Date(claim.at * 1000)}`);
}

Because the key set is public and the check runs anywhere, an attestation can live in a static page, a report, or a git archive and still be checkable long after — no call back to Tapback, no trust in whoever is showing it to you.

What it does and doesn't say

An attestation says: Tapback counted these reactions for this page at this time, under the rate limits and dedup that govern every count. It does not certify that every reactor is a distinct human — no anonymous system can. The number is bounded and costly to game, and the attestation says exactly which number it is.

Attestations are a paid-plan feature; ask if you'd like them enabled for your Tapbar.