kiosk-402

Two iframes, two jobs.

One takes payment for content you host. The other carries a piece from the kiosk-402 catalog onto your page and shows it there. Both are a single tag, both settle in USDC, and neither sends your reader away.

Mode 1

Your content, our payment

You host the article, the download, the video, the anything. The button takes the money and hands your page a signed token saying who paid. The USDC settles to your wallet in that transaction — it never rests with us — and we bill 5% to a prepaid balance afterwards.

Use this when the content is yours and you just want to be paid for it.

/embed/pay/<paywall id>

Mode 2

Our content, on your page

You embed something published on kiosk-402. Your reader pays and the piece unfolds inside the frame — they never leave your site. The money splits 50/50 with its creator, exactly as it would on our storefront.

Use this to carry someone else's work — or your own kiosk listing — without rebuilding a paywall.

/embed/item/<slug>

This is the button. It works.

Not a screenshot. That is a live paywall, priced at one cent, settling on Base or Solana. Click it and you will buy something.

your-site.com

The thing you're selling

Your article, your download, your video — hosted by you, on your page. The paywall is the only part that isn't yours.

Everything above, in full:

<iframe
  src="https://kiosk-402.unsubscribe.llc/embed/pay/YOUR_ID"
  style="border:0;width:100%;max-width:380px;height:260px"
  title="Pay with USDC"></iframe>

That is the whole integration if all you want is to be paid. Read on for the part where your page reacts to it.

Mode 2: a kiosk piece, read in place

A real article from the catalog. Pay and it opens inside the frame — no redirect, no second page.

your-blog.com

Worth reading elsewhere

You liked a piece. Put it in front of your readers and take a cut, instead of linking away.

The whole thing:

<iframe
  src="https://kiosk-402.unsubscribe.llc/embed/item/what-is-x402"
  style="border:0;width:100%;height:300px"
  title="Read on kiosk-402"></iframe>

Any published article or video. The creator is paid their half automatically; you are embedding their work, not republishing it.

It adapts to your page

Appearance is set in the URL, so you don't have to fight our CSS or ship ours.

?theme=light
?theme=dark
?compact=1&label=… — just the button
?frame=none&title=…&desc=… — borderless, your words

Colour

Hex values, with or without the #. Both modes take the same parameters.

?accent=1e6fff&accentText=ffffff&buttonRadius=24
?accent=00c48c&radius=0&buttonRadius=0
accentButton colour, as hex. accentText sets the label colour on it.
bg / text / borderCard background, text and border, as hex. Override individually.
radius / buttonRadiusCorner radius in px, 0–32. 0 for square.
themeauto (default), light, dark. Auto follows the reader's system setting.
compact1 renders the button alone — for a page that already shows its own price.
framenone drops the card border and background so it sits inside your own container.
labelButton text. Defaults to the price.
title / descOverride what the item is called on your page.

Reacting to a payment

The frame posts a message up when someone pays. The token is a signed JWT — your server checks it and decides what access means.

<script>
window.addEventListener('message', (e) => {
  if (e.origin !== 'https://kiosk-402.unsubscribe.llc') return;

  // Fit the frame to its contents — no guessed height.
  if (e.data?.type === 'kiosk402:height') {
    document.querySelector('#paywall').style.height = e.data.height + 'px';
  }

  if (e.data?.type === 'kiosk402:paid') {
    // e.data.token is a JWT signed with this paywall's verify_secret.
    fetch('/unlock', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ token: e.data.token }),
    }).then(() => location.reload());
  }
});
</script>

On your server — verify it offline, with any JWT library:

import jwt from 'jsonwebtoken';

const claims = jwt.verify(token, process.env.KIOSK_PAYWALL_SECRET);
// { item: 'pw_…', payer: '0x…', sale: '0x…', iat, exp }

if (claims.item !== 'pw_…') throw new Error('wrong paywall');
grantAccess(claims.payer, claims.exp);   // exp is already enforced above

The secret belongs to one paywall, so a leak compromises that item and nothing else. sale is the on-chain nonce — unique per purchase, and a good idempotency key if you record entitlements.

What you should know before shipping it

The money is yours immediately
pay_to is your address. The buyer's USDC reaches you in the settling transaction and never rests with us. We charge 5% (minimum half a cent) to your prepaid credits, after the sale confirms — so an unpaid fee stops new sales rather than holding your money.
The wallet step leaves the frame, deliberately
Clicking opens checkout on our origin. Someone approving a payment should be able to see whose address bar they're looking at, and inside a cross-origin iframe they can't. It returns to your page on its own.
Buyers need no gas, ever
We front the network fee and quote it into the price they sign for. They need USDC and nothing else — no ETH, no SOL.
You can restrict who embeds it
Set allowed_origins when you create the paywall and the button only works on your domains. Leave it empty and it works anywhere, which is usually what a button is for.
This is access control, not DRM
A token gates the fetch, not the screen. Anyone who can read a thing can copy it. That's true of every paywall; it's worth saying out loud.

Make one

Create a paywall from the developers page or the API. You get an id, an embed snippet, and the verify secret — shown once.

curl -X POST https://kiosk-402.unsubscribe.llc/api/v1/paywalls \
  -H "authorization: Bearer $KIOSK_KEY" \
  -H "content-type: application/json" \
  -d '{"title":"My paid article",
       "price_minor":500000,
       "pay_to_evm":"0xYourWallet",
       "access_ttl_seconds":604800}'

price_minor is micro-USDC: 500000 is fifty cents. Full reference in the API docs.