PCI DSS 4.0 for Self-Hosted Checkouts — chapters 1-3

Updated 27 Oct 2026

Free sample: chapters 1–3

This sample covers the three chapters that tell you where you stand. The rest of the book is the work: building the inventory, detecting change, writing the policy, and filling the questionnaire.

Scope note. This is an implementation guide written by people who build payment integrations. It is not a QSA opinion and it cannot be one. Your acquirer — not your gateway, and not this book — decides which questionnaire applies to you. Everything here is written so you can take it to them and have a short conversation instead of a long one.


Chapter 1 — What your acquirer is actually going to ask you

Nobody wakes up wanting to understand PCI DSS. What happens is an email: your acquiring bank, or your payment provider, asks you to complete a Self-Assessment Questionnaire. You open it and it asks about network segmentation and anti-virus deployment, and you run a shop that sells ceramics.

The reason it feels absurd is that you have probably been handed the wrong one. There are several SAQs, and which applies to you is decided by one thing: how card data gets from your customer's keyboard to your processor, and whose code is in the way.

Get that question right and most of the questionnaire disappears. Get it wrong and you either answer hundreds of irrelevant questions or — much worse — attest to something that is not true about your own shop.

The three that matter to a self-hosted shop

SAQ A is the short one. Historically it was for merchants who had outsourced payment entirely: either you redirect the customer away to the processor, or your payment form is an iframe served by the processor.

SAQ A-EP is the long one. It is for merchants whose own website affects the security of the payment transaction but who do not themselves store, process or transmit card data on their servers. The classic trigger: your page collects card details in fields your site controls and hands them to the processor from the browser.

SAQ D is the longest, for everyone else.

The gap between SAQ A and SAQ A-EP is the difference between roughly two dozen questions and well over a hundred, plus external vulnerability scanning. This is the single most expensive decision in this book, and it is decided by markup you can read in thirty seconds. Chapter 3 shows you how.

What changed, and when

Two dates matter.

31 March 2025. PCI DSS v4.x arrived with a set of requirements marked "future-dated" — best practice at first, mandatory from this date. Fifty-one of them became mandatory at every assessment. Two are about your payment page:

  • Requirement 6.4.3 — every script loaded and executed on the payment page must be authorised, its integrity assured, and an inventory maintained with a written justification for why each one is necessary.
  • Requirement 11.6.1 — a change- and tamper-detection mechanism must alert you to unauthorised modification of the HTTP headers and content of the payment page, checked at least weekly.

Read those twice. Not your server. Not your database. The page in the customer's browser. This is PCI catching up with the fact that card skimming moved into JavaScript roughly a decade ago, and that the skimmer usually arrives through a legitimate script that someone added for legitimate reasons.

January 2025. The Council revised SAQ A. Requirements 6.4.3, 11.6.1 and 12.3.1 were removed from the SAQ A question list — and replaced with something sharper: an eligibility criterion. To use SAQ A at all, you must confirm:

"The merchant has confirmed that their site is not susceptible to attacks from scripts that could affect the merchant's e-commerce system(s)."

That is not a question you answer. It is a condition you have to meet before you are allowed to use the short form. If you cannot honestly affirm it, you are not eligible for SAQ A — and you are looking at A-EP.

The part almost everyone gets wrong

In February 2025 the Council published FAQ 1588 to explain that criterion, and it contains the most useful sentence in this whole area:

The script criterion applies only to merchants whose page includes the provider's embedded payment page or form — an iframe, hosted fields, a modal. It does not apply to merchants who redirect the customer to the provider, or who outsource payment entirely.

So the first useful thing you can do is find out which of those you are. If your checkout sends the customer away to the bank's own page and they come back afterwards, this criterion is not yours to meet, and a great deal of what you are about to be sold is not necessary. If your checkout keeps the customer on your page and drops the provider's payment form into it, it is yours, and you have work to do.

FAQ 1588 gives two ways to confirm it:

  1. Protect the page yourself using techniques such as those described in 6.4.3 and 11.6.1 — deployed either by you or by a third party on your behalf.
  2. Get confirmation from your provider — a PCI DSS compliant TPSP whose embedded payment solution, implemented according to its instructions, includes protection against script attacks.

Note the phrasing: "such as, but not limited to." The FAQ does not say 6.4.3 and 11.6.1 are the only route. Vendors selling client-side monitoring sometimes present it that way. It is not what the text says.

Route 2 is the cheap route and most merchants should try it first. It costs one email. Chapter 11 of the full book has the template, because the wording matters: you want a written statement about protection of the payment page against script attacks, not a copy of their Attestation of Compliance. Those are different documents answering different questions, and an AOC on its own does not satisfy the criterion.

One more useful carve-out from the same FAQ: a provider of scripts unrelated to payment processing is not treated as a TPSP for SAQ A purposes, provided those scripts cannot affect cardholder data or sensitive authentication data. That sounds like relief for your analytics tag. Read it carefully — the condition is that the script cannot affect card data. A tag manager that can inject arbitrary JavaScript into the payment page does not meet that condition, and tag managers are how a large share of real skimming incidents actually happened.

What this means for you, in one paragraph

Your gateway's compliance is not your compliance. Your processor can be perfectly PCI DSS compliant while your payment page is wide open, because the page is yours. The affirmation on that form is yours too — your name goes on it. The rest of this book is about being able to make that affirmation honestly, with evidence, in about a day's work rather than a consulting engagement.


Chapter 2 — Find your payment page

Before anything else: open the page these requirements are about.

Most merchants cannot name it, and a surprising number of agencies get it wrong. The error is almost always the same, and it is expensive, because every control you build gets applied to the wrong URL.

The WooCommerce flow, honestly

The path a customer takes is:

/cart  →  /checkout  →  /checkout/order-pay/1482/?key=wc_order_…  →  /checkout/order-received/1482/

Most people assume the payment page is /checkout. For a great many gateways, it is not.

Here is what actually happens. When the customer presses "Place order", WooCommerce calls your gateway's process_payment(). A gateway that needs to show a payment interface does not render it there — it cannot, the request is already committed. What it does instead is create the order and return a redirect:

public function process_payment($order_id)
{
    $order = wc_get_order($order_id);
    // …create the payment at the provider…
    return ['result' => 'success', 'redirect' => $order->get_checkout_payment_url(true)];
}

get_checkout_payment_url(true) is the order-pay page. That is where the provider's script gets loaded, where the iframe gets mounted, and where the customer types their card number. In our own catalogue of ten gateways, eight work exactly this way; the other two leave the site entirely from process_payment() and never show a payment interface on our domain at all.

So:

  • The scripts you must inventory under 6.4.3 are on order-pay, not /checkout.
  • A Content-Security-Policy applied to /checkout protects nothing that matters.
  • A tamper-detection job pointed at /checkout will report "no change" forever while the actual payment page changes underneath it.

This is not a subtle point, but it is almost never written down, because the people writing about PCI do not build gateway plugins and the people building gateway plugins do not write about PCI.

Finding yours, exactly

Do this on your live site. It takes four minutes and it is the foundation of everything else.

  1. Put something cheap in the cart and go to checkout.
  2. Fill in the billing details and choose the payment method you actually want to audit. Do this once per payment method — different gateways produce different pages.
  3. Press "Place order" — and then stop. Do not pay.
  4. Copy the URL from the address bar. That is your payment page. It will look like https://yourshop.com/checkout/order-pay/1482/?pay_for_order=true&key=wc_order_…
  5. View source on it — actual source, not the inspector. Search for <script.

That list of scripts is the thing requirement 6.4.3 is about. Every one of them runs in the same browser context as your customer's card number. Every one of them can read the fields, move the form, or quietly post a copy elsewhere.

Keep that order unpaid. You will want it again, because the page only renders while the order is unpaid — and so does the tooling in chapter 4.

If you would rather not do it by hand, the companion tool does exactly this and attributes each script to the plugin, theme or vendor that put it there:

$ wp ap-pci scripts
Using order #1482 (newest unpaid).
Payment page: https://yourshop.com/checkout/order-pay/1482/?pay_for_order=true&key=wc_order_…
9 scripts, 3 of them third party.

+-------+------------------------------+------------------------------------------------------+-----+
| party | owner                        | script                                               | sri |
+-------+------------------------------+------------------------------------------------------+-----+
| first | WordPress core               | …/wp-includes/js/jquery/jquery.min.js?ver=3.7.1       | -   |
| third | third party: cdn.tagman…     | https://cdn.tagmanager.example/gtm.js?id=GTM-XYZ      | -   |
| third | third party: checkout.ra…    | https://checkout.razorpay.com/v1/checkout.js          | -   |
| first | plugin: ap-razorpay          | …/plugins/ap-razorpay/assets/pay.js?ver=1.0.0         | -   |
| first | theme: storefront            | …/themes/storefront/assets/js/main.js                 | -   |
| first | inline on the page           | (inline) var aprzData = {"order":1482,"key":"wc_or…   | -   |
+-------+------------------------------+------------------------------------------------------+-----+

The column that should make you uncomfortable is owner. A tag manager on the payment page is a third party with permission to run arbitrary code next to a card number. It may be entirely justified — but 6.4.3 wants the justification written down, and when you write it down you often decide you did not need it there after all.

Blocks, shortcodes, and other carts

WooCommerce Blocks vs shortcode checkout. The block checkout changes /checkout substantially but does not change where order-pay lives. Gateways supporting Blocks register a payment method type for the checkout step and still hand off to order-pay. Audit order-pay either way.

Express buttons are a second payment page. A wallet button on the cart or product page starts a payment outside the flow above. If you have one, it is in scope and it has its own script set. Inventory it separately.

OpenCart keeps the payment step in the confirm stage of the checkout route, usually loading the provider's script into the confirm panel. The page to audit is the one the confirm step renders, not the cart.

PrestaShop splits order and order-confirmation; most modules render payment into the order step via a hook template. Audit the order step with your module selected.

Bitrix renders payment inside the sale order component, and the template that does it is usually a copy inside your site's own local/ tree — which means you own that markup and any script in it, not the vendor.

Appendix E of the full book has the per-cart detail. The principle does not change: the payment page is wherever the card number is typed, and you must be able to point at it.


Chapter 3 — The four integration shapes

Everything about your obligations follows from one question: whose code touches the card number?

There are four answers. Find yours and you will know which questionnaire you are eligible for, whether the script criterion applies to you, and what work is in front of you.

Shape 1 — Full redirect

The customer leaves your site. Your server sends them to the provider's own page, they pay there, and they come back to a return URL. Nothing of the provider's runs on your domain.

In our catalogue, two gateways do this: process_payment() calls the provider's API, gets a URL back, and returns it as the redirect. The customer never sees a payment interface on our domain.

  • Script criterion: does not apply (FAQ 1588).
  • Questionnaire: SAQ A, most comfortably of all the shapes.
  • Your remaining work: the return and webhook handling, which is chapter 8 — and which is where these integrations actually go wrong.

Shape 2 — Server-side redirect with a signed payload

Same experience as shape 1, but your server first negotiates with the provider and then posts a signed blob to them, usually by rendering a form that submits itself.

One of ours does this: the server calls the provider's API, receives a redirect link and an opaque signed parameter, and hands the browser to the provider with that payload.

  • Script criterion: does not apply. The customer still leaves.
  • Questionnaire: SAQ A.
  • Your remaining work: the signing key is now a secret on your server that matters (chapter 9), and a self-submitting form is a page you control that could be tampered with, so 11.6.1-style monitoring is still a good idea even when not strictly required.

Shape 3 — The provider's form, embedded in your page

The customer stays on your page. You load the provider's JavaScript, and it mounts its own iframes — hosted fields, a modal, an overlay. The card number is typed into the provider's iframe, on the provider's origin. Your JavaScript never sees it.

This is now the most common shape, and five of our ten gateways work this way. The pattern in markup looks like this — note that the field containers are empty:

'<span id="apsx-number"></span>'   // provider's iframe is injected here
'<span id="apsx-cvv"></span>'

and in script:

var stax = new window.StaxJs(token, {
    number: { id: 'apsx-number', … },
    cvv:    { id: 'apsx-cvv', … }
});

Your code names a place. The provider's code fills it. The card number crosses an origin boundary your JavaScript cannot reach across.

  • Script criterion: applies. This is precisely the case FAQ 1588 was written for.
  • Questionnaire: SAQ A remains available — if you can affirm the criterion.
  • Your work: the inventory (ch. 4), the integrity question (ch. 5), tamper detection (ch. 6), and a CSP that does not break the pay button (ch. 7). Or one email to your provider under route 2, which you should send today.

A warning specific to this shape. The iframe protects the card number from your JavaScript. It does not protect it from a script that replaces the iframe. A skimmer with execution on your page can remove the provider's hosted field and put a convincing look-alike input in its place, and the customer cannot tell. That is the attack 6.4.3 and 11.6.1 exist to catch, and it is why "the card data is in an iframe" is a reason to feel better, not a reason to skip the chapter.

Shape 4 — Card fields in your own form

Your page has real inputs. The card number is typed into markup your site owns, and your JavaScript reads the value — even if the very next thing it does is hand it straight to the provider's SDK for tokenisation and never send it to your server.

In markup it is unmistakable:

'<input id="appj-number" autocomplete="cc-number" …>'
'<input id="appj-cvv" type="password" autocomplete="cc-csc" …>'

and in script:

var n = number.value.replace(/\D/g, '');   // the PAN, in your own JavaScript

One of ours does this, because that provider's SDK works this way.

  • Questionnaire: SAQ A is not available to you. The script criterion is not the issue; eligibility for SAQ A never applied in the first place, because your page is not displaying a provider-hosted form. You are in SAQ A-EP territory, which brings the full client-side requirement set and quarterly external vulnerability scanning by an approved vendor.
  • Your work: all of it, and 6.4.3 and 11.6.1 apply to you directly rather than through an eligibility criterion.

This shape is not wrong and it is sometimes the only option a provider offers. But it is a materially different compliance position from shape 3, and the difference is invisible on screen — both look like a card form on your checkout. If you are running this shape without knowing it, you may be attesting on SAQ A today to something that is not true of your shop. That is the most common serious finding in this whole subject, and it is why this chapter is in the free sample.

Telling them apart in three checks

On your payment page from chapter 2:

  1. Do you leave the site to pay? Yes → shape 1 or 2, and the script criterion is not yours. Stop here.
  2. View source and search for autocomplete="cc-number". Found it as an <input> → shape 4. Found it as an empty <span> or <div>, or not at all → shape 3.
  3. Check the field in the browser inspector. If the box you can type a card number into sits inside an <iframe> whose src is on the provider's domain → shape 3, confirmed.

Check 2 is the one that decides your questionnaire. It takes ten seconds and it is worth more than any report you can buy.

Where this leaves you

Leaves your siteProvider's script on your pageCard number in your DOMScript criterionSAQ A available
Shape 1 — full redirectyesnononoyes
Shape 2 — signed redirectyesnononoyes
Shape 3 — embedded formnoyesnoyesyes, if affirmed
Shape 4 — your own fieldsnoyesyesn/ano

Write down which shape each of your payment methods is. If you offer three methods you may well be in three different shapes at once, and your obligations are set by the strictest one.


What the rest of the book does

Chapters 1–3 told you where you stand. The remaining nine do the work:

4. Build the script inventory — the 6.4.3 deliverable, by hand and then with tooling, including the WP-CLI command that dumps the live list and attributes every script to whatever put it there.

5. Why you cannot SRI your processor — the honest chapter. Subresource Integrity is the obvious answer to "assure the integrity of each script," and for your provider's CDN script it is usually impossible: checkout.razorpay.com/v1/checkout.js carries no version and changes without notice, so there is no hash to pin. What to do instead, and where SRI genuinely does apply.

6. Tamper detection a weekly check will pass — 11.6.1 in three tiers by budget, with working code for the cheapest one and a cron entry that mails you on change.

7. A CSP that does not break checkout — built from your own inventory, report-only first, and the failure mode that looks exactly like a gateway outage.

8. Webhooks, signatures and replay — the server side nobody audits. Verify before you parse, hash_equals not ==, bind to the order key, and make a replayed success impossible to settle twice.

9. Secrets and the test/live split — including why a test-mode script host must never reach production.

10. Proving it — testing against a mock provider, forging a bad signature to confirm your code refuses it, and what an assessor will accept as evidence.

11. Filling the questionnaire — question by question, per shape, with the letter template for route 2.

12. Keeping it true — the quarterly rhythm, what a plugin update breaks, and the tag-manager problem that undoes most of this work six months later.

Plus: the inventory worksheet, the provider letter template, the one-page decision chart, a per-gateway reference table, and per-cart notes for WooCommerce, OpenCart, PrestaShop and Bitrix.


Verify this yourself

Every date and quotation in this sample comes from PCI SSC primary sources — SAQ A r1 for PCI DSS v4.0.1, FAQ 1588, and the v4.x future-dated requirement list. Do not take our word for any of it; secondary sources on this subject contradict each other routinely, including on the date of the SAQ A revision. Go to the Document Library and read SAQ A r1 yourself. It is shorter than this chapter.

And then ask your acquirer which questionnaire they expect from you. They decide. Being able to tell them which shape you are in is what makes that a two-email conversation.