# Embedded Payments Compared With In-Page Card Collection

How a gateway-hosted embedded payment form differs from a direct card-collection script, and what changes for your servers when you move.

**Category:** Integration

**Last reviewed:** 5 August 2026

# Embedded Payments Compared With In-Page Card Collection

Two integration styles let a shopper pay without leaving your checkout page. This guide describes both, states the differences that matter to a security review, and maps the older style onto the current one field by field.

The older style is generically known as a **direct card-collection script**, or an **in-page tokenization widget**. A script supplied by the gateway renders card input elements into your own document, exchanges the entered card for a token in the browser, and hands that token back to your JavaScript, which posts it to your server together with the order amount. Your server then calls a charge endpoint with the token.

The current style is an **embedded payment session**. Your server creates a session that states the amount and what is being paid for. Your page then mounts a frame that the payment host serves, the shopper submits inside that frame, and your page receives typed lifecycle events while your server receives a webhook.

## Identifying which model you have

Your integration is a direct card-collection script if all of the following are true:

- Your checkout page loads a gateway script from a fixed URL that does not change between releases.
- Card number, expiry, and security code elements are part of your own document, whether the script created them or you did.
- The amount, and usually a publishable key, are configured in browser code.
- A callback hands a token to your JavaScript.
- Your server exposes an endpoint that accepts that token plus an amount and performs the charge.

Your integration is an embedded payment session if all of the following are true:

- Your server calls a session-create endpoint before the payment form is rendered, and receives a session URL.
- The card fields live inside a frame served by the payment host, so your page cannot read them.
- Nothing in your page carries a payment credential.
- Your page subscribes to a documented event protocol rather than reading form fields.
- A webhook, not a synchronous response to your own charge call, is the record that a payment happened.

The rest of this guide assumes the first list describes your current integration.

## PCI scope

Your validation requirements are determined by your acquirer and your assessor, against your whole cardholder data environment. Nothing below is a determination of your eligibility for any particular self-assessment questionnaire. What follows describes how each integration works, so that you can answer an assessor's questions accurately.

**Direct card-collection scripts.** The card fields are elements in your document, and every script on that page can read the document. Your checkout page therefore controls the capture of cardholder data, even though the data is tokenized in the browser and never reaches your server. That places the page in the territory covered by the e-commerce questionnaire for merchants whose website does not receive cardholder data but does control how it is captured, commonly referred to as SAQ A-EP. Practically, that questionnaire is several times longer than the outsourced one, and it adds obligations that fall on your own web estate: secure development and change control for the checkout application, vulnerability management, quarterly scanning by an approved scanning vendor, and penetration testing of the environment hosting the page.

**Embedded payment sessions.** The card fields are rendered by the payment host inside a frame the payment host serves, and the browser's same-origin policy prevents your page from reading them. The submission goes directly from the shopper's browser to the payment host. That is the outsourced-iframe shape the shortest e-commerce questionnaire, commonly referred to as SAQ A, is written around.

Two qualifications apply to the second case:

- **Your other channels are assessed too.** A merchant who also takes card numbers over the phone, on paper, or through a second integration is assessed on those channels regardless of how clean this one is.
- **Your page does not leave scope entirely.** Every script on a payment page can read that page. Knowing which scripts are present, why each is there, and detecting when one changes remains your responsibility under PCI DSS 6.4.3 and 11.6.1. The next two sections describe what the platform contributes to that answer.

## Amount integrity

In the in-page model the amount is a value in browser code. Anyone can edit it before the tokenize call, so the amount your JavaScript posts alongside the token is an assertion by an untrusted party. The only correct defence is for your server to ignore the posted amount entirely and re-derive it from its own record of the order. Integrations that instead charge the amount they were sent are the recurring failure of this model.

In the session model the amount is decided before the shopper ever sees a form. Your server states it on the session, and the browser is never told what it may charge. A session can additionally declare a locked amount, in which case the field renders read-only and the submitted value is re-verified server-side against the session before the charge proceeds. A mismatch is rejected rather than charged.

The session URL is the only capability the browser holds. It is single-use, expires in minutes, and authorizes exactly the one payment your server described.

## Script supply chain

A script on a checkout page can read the page it is on. If the bytes behind a script tag can change without the tag changing, then whoever can change those bytes can change what runs on every checkout page that includes it, at once, with no deployment on your side and nothing for you to notice. That is a property of how the script is hosted rather than of the script itself, and it is why a fixed, unversioned script URL is the weakest part of the older model.

The embedded SDK is published so that the load is verifiable, through two mechanisms that are meant to be used together:

1. **The URL names its own content.** Published artifacts are served from a path carrying both a version and a hash of the exact bytes at that path. Changing any byte changes the hash, and therefore the path, so there is no way to express "the same URL, different content". There is deliberately no unversioned or `latest` alias. The payment host enforces its half: it re-derives the hash from the bytes it is about to serve and refuses to serve a file whose content does not re-derive the hash in its own URL.
2. **Your tag pins the hash.** Subresource Integrity puts the check in the shopper's browser. The `integrity` value for every artifact is published in a release manifest your deployment tooling can read. A cross-origin script tag must also carry `crossorigin="anonymous"`, or the browser silently loads the script without checking it.

A brand loader is available for integrations that would rather not update their own tag on every release. It is a small script that resolves the current published release and injects the payment code with the integrity attribute already set. If the payment host has no verified release, that endpoint fails rather than serving an unpinned load: a pin that disappears quietly is worse than an outage you can see.

## The integration contract

The two models differ in what your code is coupled to.

**Direct card-collection scripts** couple your page to a set of form fields and a callback. Your code reads values out of the document, hands them to a script, and receives a token. There is no versioned contract for the shapes involved, so a change on either side surfaces as a runtime failure on a live checkout.

**Embedded payment sessions** couple your page to a typed event protocol. Every message carries a protocol version, a source discriminator, and the session id, and each message type has a documented payload shape. The protocol is additive within a major version: message types and payload members may be added, but nothing is renamed, removed, or repurposed, and a client is expected to ignore message types it does not recognise. Your page validates three things before trusting a message: the exact origin, the source discriminator, and that the session id matches the session it mounted.

The division of responsibility on your server changes accordingly:

| Responsibility | Direct card-collection script | Embedded payment session |
|---|---|---|
| Before the form renders | Nothing | Create the session: amount, order reference, and your own authorization checks |
| While the shopper pays | Receive the token from your page, then charge it | Nothing |
| Immediately after | Read the synchronous charge response | Read the browser event, for display only |
| Record of the payment | The synchronous charge response | The webhook |

The last row is the substantive change. A shopper whose connection drops between the approval and your redirect has still paid, and only the webhook tells you so. If your current integration treats the synchronous charge response as its only record, that gap exists today as well; the session model simply makes it visible.

## Compliance content the form renders for you

A payment form frequently has to present, capture, and retain text that is not yours to invent. In the in-page model each of these is your responsibility to author, display at the right moment, record against the transaction, and retain in a form that survives a dispute. In the session model the hosted form does it, and WinkPG writes the evidence onto the transaction and delivers it on the completion webhook.

- **Stored-credential consent.** When a session stores the card for later use, the form renders the consent text for the authorized usage scope, records that the shopper agreed, and versions the exact text shown so a later dispute can be answered with the wording the shopper actually saw. Card and bank-account wording are separate, and the version recorded is the one for the instrument that was charged.
- **ACH authorization.** A bank-account debit taken over the internet is a NACHA WEB entry, and the authorization language, a version hash of it, the consumer IP address, and a timestamp all have to be retained. The form captures all four. Merchants who need their own approved wording can configure it per page.
- **Convenience-fee disclosure.** Where a convenience fee is shown, WinkPG renders a canonical disclosure block, requires acknowledgement, and records the acknowledgement against the exact fee amount presented.
- **Surcharge disclosure.** Where a credit-card surcharge applies, the form discloses it live as the card is entered, shows a surcharge-inclusive total, and records the disclosure against the amount shown.

Each of these has to be reimplemented, and kept current, by any integration that renders its own card fields.

## Migration mapping

| Direct card-collection script | Embedded payment session |
|---|---|
| Script tag at a fixed, unversioned URL | Content-addressed artifact, or the brand loader, with the integrity attribute applied |
| Amount configured in browser code | Amount on the session your server created |
| Publishable key present on every page view | Nothing in the page authenticates; the single-use session URL is the only capability |
| Card fields inside your document | Card fields inside a frame your page cannot read |
| Presentation options passed from your page | Appearance configured once on the hosted page, applying to every integration that uses it |
| Tokenize callback returns a token to your JavaScript | Nothing about the instrument passes through your page |
| Your charge endpoint, taking a token and an amount | Your session-create endpoint, taking an amount and an order reference, returning a session URL |
| Synchronous charge response treated as the record | Browser event for display, webhook as the record |
| Decline handled by returning the shopper to a re-entry page | Failure event raised with the form still on screen and the session still payable |
| Consent and disclosure text you author and retain | Rendered, captured, and versioned by the form |
| Saved-card token minted and held by your page | Stored-credential reference arriving on the completion event and the webhook |

## A migration order that works

1. **Add the session-create call server-side**, alongside the charge endpoint you already have. Nothing is removed, so this step is safe to deploy on its own.
2. **Get one non-production checkout page mounting the embedded form.** Your existing integration keeps running everywhere else.
3. **Get each of your origins onto the hosted page's allowed embedding domains**, for every environment. This is a configuration request rather than a code change, and it is the step most likely to add a day, so raise it early.
4. **Subscribe to the payment webhooks and reconcile them against your orders** before you switch any traffic. Doing this while the old flow is still live lets you compare the two records against each other.
5. **Switch one checkout path**, observe it, then move the rest.
6. **Remove the old script tag, your token-posting code, and your charge endpoint**, in that order. Leaving the script tag in place leaves a mutable third-party script on a live checkout page, which is the specific exposure this replacement exists to remove, so do not stop at step 5.

## What does not carry over

- **Publishable keys in the page.** There is no equivalent, and nothing in the page needs one.
- **Presentation options passed from your page.** Appearance is configured on the hosted page.
- **A token handed back to your JavaScript.** If you need a stored credential for later charges, that is a property of the session, and its reference arrives on the completion event and on the webhook rather than being minted by your page.
- **An unversioned script URL.** There is deliberately no unversioned or `latest` path to point at.

## See also

- [The Embedded Payments SDK](/help/guides/embedded-payments-sdk): the browser loader that replaces the script tag described above, with its options, events, script pinning, and policy requirements.
- [Hosted Payment Page Iframe Integration](/help/guides/hpp-iframe-integration): the event protocol, the command channel, and the origin checks a parent page must perform.
- [Setting Up a Hosted Payment Page](/help/guides/hosted-payment-page-setup): configure the page the embedded session renders.
- [Webhook Integration](/help/guides/webhook-integration): the wire format, signature scheme, and delivery semantics for the record of truth described above.
- [Reusing a Saved Card with Payment Tokens](/help/guides/reusing-saved-cards): what replaces a token your page used to hold.

## See also

- [All documentation](https://docs.winkpg.io/llms.txt): the machine-readable index of every public page on this site.
