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.
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.
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 wordsColour
Hex values, with or without the #. Both modes take the same parameters.
?accent=1e6fff&accentText=ffffff&buttonRadius=24?accent=00c48c&radius=0&buttonRadius=0accent | Button colour, as hex. accentText sets the label colour on it. |
|---|---|
bg / text / border | Card background, text and border, as hex. Override individually. |
radius / buttonRadius | Corner radius in px, 0–32. 0 for square. |
theme | auto (default), light, dark. Auto follows the reader's system setting. |
compact | 1 renders the button alone — for a page that already shows its own price. |
frame | none drops the card border and background so it sits inside your own container. |
label | Button text. Defaults to the price. |
title / desc | Override 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_tois 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_originswhen 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.