No iframe. No scripts.

A payment button that's just an anchor tag

Stripe Buy Button is an iframe. PayPal Buttons is a JavaScript SDK. Snipcart is a runtime. Every "easy" payment embed today is a third-party script that pulls in cookies, tracking, and CSP exceptions. Here's how to ship a real payment button with none of that.

Payment button without an iframe
Hidden cost

What an iframe payment button costs you

Embedding an iframe checkout looks innocent — one <iframe> in your HTML and you're done. The hidden cost shows up later. The iframe pulls in third-party JavaScript that runs on every page that has the button. That JavaScript drops cookies. Those cookies trigger your GDPR banner. The banner blocks rendering until the user accepts. The accepted scripts then load over network, hurting your Largest Contentful Paint. Your CSP has to explicitly allow the third-party origins. Your security team flags it on the next audit. You spent two minutes embedding a button and bought yourself an afternoon of compliance work.

  • Iframes load third-party JavaScript that runs in your origin's context.
  • Third-party scripts set cookies, triggering GDPR consent banners.
  • CSP rules need explicit script-src and frame-src exceptions.
  • Lighthouse penalises render-blocking third-party scripts.
  • Subresource Integrity (SRI) is impractical for vendor SDKs that change.
  • Security audits flag any third-party script as a supply-chain risk.
The fix

What a script-free payment button looks like

PayRequest's Payment Button is two things: a <link> tag pointing to a CSS file on PayRequest's CDN, and an <a> anchor with a className. There's no <iframe>, no <script>, no inline JavaScript, no popup-opener call, no postMessage handshake, no third-party cookies. The button is HTML and CSS — the same primitives the web shipped with in 1995. Customers click; the browser navigates to your branded checkout in a new tab. That's the entire architecture.

Zero third-party JavaScript

No <script> tags from PayRequest run on your page. No tracking pixel, no analytics beacon, no SDK runtime.

Zero cookies before the click

Until a customer clicks the button, no cookies are set by PayRequest on your origin. GDPR consent banners stay clean.

CSP-friendly by construction

Your script-src can stay 'self'. Your frame-src can stay 'none'. The only addition is a style-src entry for PayRequest's CSS CDN — and even that is optional with hash-based CSP.

Lighthouse stays green

No render-blocking scripts. The stylesheet is small (a few KB gzipped) and async-cacheable. Performance, Accessibility, Best Practices and SEO scores don't budge.

The network audit

What's actually loading on your page

Compare the network and security profile of common payment embeds. The PayRequest column is what your DevTools Network panel shows when the button renders.

EmbedIframesThird-party JSCookies before clickCSP changes neededLighthouse impact
Stripe Buy Button
1+Stripe.js (~90 KB)Yesscript-src + frame-srcRender-blocking script
PayPal Smart Buttons SDK
1+ on clickPayPal SDK (~150 KB)Yesscript-src + frame-srcHeavy main-thread cost
Snipcart
0Snipcart runtime (~120 KB)Yesscript-src + style-srcRuntime hydration cost
Lemon Squeezy overlay
1 (lazy)Lemon.jsYesscript-src + frame-srcLazy but measurable
PayRequest Payment ButtonBest
00 KBNonestyle-src only (optional)None
CSP diff

Your CSP, before and after

Most teams hit Content-Security-Policy as the moment they realise their payment embed is more invasive than expected. Here's how the policy changes when you swap an iframe-based button for the PayRequest anchor.

With Stripe Buy Button
script-src 'self' https://js.stripe.com;
frame-src https://js.stripe.com https://hooks.stripe.com;
connect-src 'self' https://api.stripe.com https://maps.googleapis.com;
Three directives need explicit exceptions. script-src is the security-team blocker.
With PayPal SDK
script-src 'self' https://www.paypal.com https://www.paypalobjects.com;
frame-src https://www.paypal.com;
connect-src 'self' https://www.paypal.com;
Similar to Stripe — three directives, all third-party.
With PayRequest Payment Button
style-src 'self' https://payrequest.app;
/* No script-src changes. No frame-src changes. */
Only style-src changes — and only because the stylesheet is on a CDN. Inline-host the CSS and you don't even need that.
GDPR

GDPR consent: nothing to ask

GDPR/ePrivacy consent banners exist because most analytics and embed scripts drop tracking cookies before the user has agreed. PayRequest's button doesn't drop cookies on your page until the customer clicks (and at that point, they're navigating to PayRequest's domain, where PayRequest's own cookie policy applies). On your origin, the button is functionally indistinguishable from a regular hyperlink — and regular hyperlinks don't need consent.

  • No analytics, no marketing tags, no remarketing pixels embedded by PayRequest.
  • Consent banners can keep their default 'reject all' behaviour without breaking the button.
  • Your DPA scope shrinks: the third-party processor (PayPal/Stripe/Mollie via PayRequest) only enters the picture after the click, on PayRequest's domain.
  • ePrivacy 'cookie wall' rules don't apply because no cookies are set pre-click.
Lighthouse

Lighthouse, real-world

Bytes you don't ship are the fastest bytes. The Payment Button's full integration is ~3KB of CSS over the network and 0KB of JavaScript. Below is what we see on a typical PayRequest customer's page when they replace a Stripe Buy Button with a PayRequest anchor.

Lighthouse metricWith Stripe Buy ButtonWith PayRequest buttonChange
Performance score7894+16
Largest Contentful Paint2.4s1.6s-0.8s
Total Blocking Time320ms40ms-280ms
JS payload~110 KB0 KB-110 KB
Third-party requests8–121 (CSS)-7 to -11

Numbers are typical, not promises — your baseline depends on the rest of your stack. The direction is what matters.

Walkthrough

How to ship a script-free payment button

01

Create a Smart Link in PayRequest

Sign in → Payment Page → Smart Links → New. Set the amount, pick methods. Copy the URL.

30 sec
02

Add the stylesheet to your <head>

Paste <link rel="stylesheet" href="https://payrequest.app/embed/button.css" />. If your CSP forbids external CDNs, copy the CSS file into your own static assets and self-host it instead.

10 sec
03

Drop the anchor where the CTA belongs

<a class="pr-btn pr-btn--default" href="..." target="_blank" rel="noopener">Pay</a>. That's the whole HTML — no <script>, no <iframe>, no <noscript> fallback to write.

10 sec
04

Verify in DevTools

Open Network → reload. The only PayRequest request you should see is the CSS file. No fetch, no XHR, no eval, no postMessage. Your security team will love this.

30 sec
05

Audit and ship

Run Lighthouse on the page. Compare with the previous snapshot. Update your CSP to drop the iframe/script exceptions for the old vendor. Done.

5 min
Who needs this

Who needs a script-free payment button

Static-site blogs and marketing sites

Hugo, Jekyll, Astro and 11ty sites that pride themselves on shipping no JavaScript. The Payment Button preserves that.

Privacy-first products and EU SaaS

Companies whose product positioning includes 'no third-party trackers' can't undo that with a payment iframe.

Lighthouse-perfect landing pages

Marketing teams chasing Core Web Vitals don't want a payment SDK costing them 200ms of TBT.

Internal tools behind strict CSP

Enterprise admin dashboards with locked-down CSP that legitimately can't add script-src exceptions for vendor SDKs.

FAQ

Frequently asked questions

Is the Payment Button really an anchor tag, not a hidden iframe?+
Yes. View source on any PayRequest button and you'll see <a href="..." class="pr-btn ...">. There's no JavaScript that swaps it for an iframe at runtime, no postMessage handshake, no shadow DOM. It's a regular hyperlink that happens to be styled to look like a button.
Does that mean the checkout itself runs on PayRequest's domain?+
Yes — and that's the point. The customer clicks, the browser navigates to payrequest.me/yourhandle/... in a new tab, and the checkout runs there. PayPal/Stripe SDKs do the inverse: they run vendor code on your domain. PayRequest runs PayRequest code on PayRequest's domain. Your domain stays clean.
Can I self-host the stylesheet to avoid even the style-src CDN exception?+
Yes. Download the CSS file from https://payrequest.app/embed/button.css and serve it from your own static assets. Update the <link rel="stylesheet"> href accordingly. Your CSP no longer needs any third-party entry.
Will Lighthouse really score better just from removing one iframe?+
On most sites, yes. The biggest wins come from removing the third-party JavaScript bundle (Stripe.js is ~90KB, PayPal SDK ~150KB, Snipcart ~120KB) and the network round-trips that come with them. Total Blocking Time, LCP, and Performance score all improve when those bytes leave the critical path.
What about analytics and conversion tracking — don't I need a script for that?+
No. PayRequest tracks every Smart Link click and conversion server-side and shows them in your dashboard. To attribute conversions to a specific page, append a query string (?source=pricing) and PayRequest groups conversions by parameter. No client-side analytics needed.
Is this CSP-strict-friendly (with nonce or hash)?+
Yes. Because there are no inline scripts and no third-party scripts, strict-CSP with nonces or hashes works without exceptions. The button is one of the rare integrations that doesn't fight strict CSP.
Script-free payments

Ship payments without shipping a runtime

Sign up, create a Smart Link, add two lines of HTML. No iframe, no SDK, no GDPR banner update, no CSP exception. Just a button that works.