pop402

pop402

Cryptographic receipts for purchases and access — sell licenses & subscriptions over HTTP, without middlemen.

           ╭─────────────────────╮
           │                     │
          ╱│                     │╲
         ╱ │                     │ ╲
        ╱  │                     │  ╲
       │   │     ┌─────┐        │   │
       │   │     │  │        │   │
       │   │     └─────┘        │   │
       │   ├─────────────────────┤   │
       │   │                     │   │
       │   └─────────────────────┘   │
       │                             │
       └─────────────────────────────┘
                
Read the docs Try it out
1. Get Challenge
// Request authentication challenge
const challenge = await fetch('https://facilitator.pop402.com/challenge', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    walletAddress: wallet.publicKey.toBase58(),
    ttl: 3600  // 1 hour session
  })
}).then(r => r.json());
2. Sign Challenge
// User signs challenge message
const message = new TextEncoder().encode(challenge.challenge.message);
const signature = await wallet.signMessage(message);
const signatureB58 = bs58.encode(signature);
3. Verify Access
// Verify with signed challenge
const result = await fetch('https://facilitator.pop402.com/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    paymentPayload: {
      x402Meta: {
        challengeId: challenge.challenge.id,
        signature: signatureB58,
        walletAddress: wallet.publicKey.toBase58(),
        sku: 'premium-content-xyz'
      }
    },
    paymentRequirements: {
      network: 'solana'
    }
  })
}).then(r => r.json());

FAQ

How does challenge-response authentication work?
1. Request challenge from /challenge with wallet address
2. Sign the challenge message with your wallet
3. Send challengeId + signature to /verify
Server verifies signature and checks license. Reusable until expiration.
How long is a challenge valid?
Default 5 minutes, configurable up to hours via ttl parameter.
Challenge can be reused for multiple requests until expiration.
No wallet popup needed after initial signature.
Does pop402 change x402 endpoints?
No. Keep /verify and /settle. Point them to pop402 and add X-PAYMENT-META.
What about revocation?
Deferred. Receipts include jti for future revocation lists.

Why receipts and licenses?

Payments prove transfer; receipts prove rights. pop402 issues cryptographic receipts with license terms so apps can grant time/usage‑based access and verify it later.

Use cases include gated downloads, timed subscriptions, single‑use content, and verifiable returns — receipts are the portable proof that an app can trust.

Quickstart

Base64url‑encode your Payment Meta and send it in the X-PAYMENT-META header.

QUICKSTART • X-PAYMENT-META header
// base64url helper
function toBase64Url(json) {
  const b64 = btoa(JSON.stringify(json));
  return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/,'');
}

const paymentMeta = {
  sku: 'premium-content-xyz',
  payerPubkey: 'Hh1k...s9eZ',
  signature: '3hF...',
  message: 'premium-content-xyz:2025-10-30T00:00:00.000Z'
};
const headerValue = toBase64Url(paymentMeta);

// fetch to an x402-protected route
await fetch('/api/protected', { headers: { 'X-PAYMENT-META': headerValue } });

# curl (base64url)
# HEADER=$(node -e "const m={sku:'premium-content-xyz'};const b=Buffer.from(JSON.stringify(m),'utf8').toString('base64').replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'');console.log(b)")
# curl -H "X-PAYMENT-META: $HEADER" https://your.app/api/protected
Tear‑off to start
pop402 • facilitator‑first receipts on‑chain