How receipt signatures work
Last updated: July 11, 2026
Before a clip is posted, Everpop records a prediction commitment: a small, immutable record of what our ranker predicted for that clip and when. The record is signed with ed25519at commit time, so it can later be checked without trusting us: the prediction wasn't rewritten after the fact, and it was made beforethe clip went live — not backfit to numbers we'd already seen.
What a receipt page shows today:the signing-key id and the SHA-256 of the commitment's stored payload. The full payload and signature aren't self-serve on the page yet — email support@everpop.app with any receipt link and we'll send both, so you can run the exact check below. A self-serve export is on the roadmap; this page will say so when it ships.
What exactly is signed — and what isn't
The signature covers the pre-publish prediction only. The measured 48-hour and 7-day numbers on a receipt come from the YouTube Analytics API and are not separately signed yet — they are real API reads, but the cryptographic guarantee on this page applies to the prediction commitment, nothing else.
The public key
The verification key is published at /api/receipts/public-key — a machine-readable JSON response with the key in two standard encodings (SPKI DER base64, and the raw 32-byte point base64url), the key's id, the payload schema version, and its own verify instructions. The publicKeyIdshown on a receipt is the first 16 hex characters of sha256 over the raw public key; match it against the endpoint's to confirm you're holding the right key after any rotation.
The canonical payload
Each commitment stores the exact string that was signed — canonical JSON with a frozen field order, versioned by its v field (currently pc1):
{
"v": "pc1",
"clipId": "...",
"userId": "...",
"modelVersion": "...",
"basis": "channel" | "cohort",
"predictedPercentile": number | null,
"predictedPercentileDisplay": number | null,
"at": "2026-07-09T14:03:22.187Z" // ISO timestamp at commit time
}Nulls are explicit so an absent prediction can't be forged into a zero. The signature is base64url-encoded ed25519 over the UTF-8 bytes of this exact string — verify against the stored payload, never a re-serialization.
A worked example: what to check
With the commitment's payload and signature in hand (see above) and the post's publish time, check two things:
import { createPublicKey, verify } from "node:crypto";
const { publicKeySpkiBase64 } = await (
await fetch("https://everpop.app/api/receipts/public-key")
).json();
const key = createPublicKey({
key: Buffer.from(publicKeySpkiBase64, "base64"),
format: "der",
type: "spki",
});
// 1. The signature verifies against the published key.
verify(null, Buffer.from(payload, "utf8"), key,
Buffer.from(signature, "base64url")); // → true
// 2. The commitment predates the publish.
new Date(JSON.parse(payload).at) < new Date(postedAt); // → trueIf both hold, the prediction on that receipt was committed before the clip was posted and hasn't changed since. If either fails, don't trust the prediction claim — and please tell us at security@everpop.app.
Key rotation
A key rotation mints a new publicKeyId; old commitments stay verifiable because each row carries its own payload and any archived copy of the public-key response still verifies it. The endpoint always serves the current key.
