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.

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.
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.
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.
| Embed | Iframes | Third-party JS | Cookies before click | CSP changes needed | Lighthouse impact |
|---|---|---|---|---|---|
Stripe Buy Button | 1+ | Stripe.js (~90 KB) | Yes | script-src + frame-src | Render-blocking script |
PayPal Smart Buttons SDK | 1+ on click | PayPal SDK (~150 KB) | Yes | script-src + frame-src | Heavy main-thread cost |
Snipcart | 0 | Snipcart runtime (~120 KB) | Yes | script-src + style-src | Runtime hydration cost |
Lemon Squeezy overlay | 1 (lazy) | Lemon.js | Yes | script-src + frame-src | Lazy but measurable |
PayRequest Payment ButtonBest | 0 | 0 KB | None | style-src only (optional) | None |
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.
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;script-src 'self' https://www.paypal.com https://www.paypalobjects.com;
frame-src https://www.paypal.com;
connect-src 'self' https://www.paypal.com;style-src 'self' https://payrequest.app;
/* No script-src changes. No frame-src changes. */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, 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 metric | With Stripe Buy Button | With PayRequest button | Change |
|---|---|---|---|
| Performance score | 78 | 94 | +16 |
| Largest Contentful Paint | 2.4s | 1.6s | -0.8s |
| Total Blocking Time | 320ms | 40ms | -280ms |
| JS payload | ~110 KB | 0 KB | -110 KB |
| Third-party requests | 8–12 | 1 (CSS) | -7 to -11 |
Numbers are typical, not promises — your baseline depends on the rest of your stack. The direction is what matters.
How to ship a script-free payment button
Create a Smart Link in PayRequest
Sign in → Payment Page → Smart Links → New. Set the amount, pick methods. Copy the URL.
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.
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.
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.
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.
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.
Frequently asked questions
Is the Payment Button really an anchor tag, not a hidden iframe?+
Does that mean the checkout itself runs on PayRequest's domain?+
Can I self-host the stylesheet to avoid even the style-src CDN exception?+
Will Lighthouse really score better just from removing one iframe?+
What about analytics and conversion tracking — don't I need a script for that?+
Is this CSP-strict-friendly (with nonce or hash)?+
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.