Skip to main content

postMessage Transport for the Widget

What is it?

A second option for delivering configuration to the checkout in widget mode. Instead of sending all parameters in the iframe URL — where they are exposed in logs, browser history, and network tools — the configuration is injected after the checkout loads, using the browser's native postMessage mechanism. This prevents sensitive data (such as encrypted tokens) from being visible in the address bar.

When do I need it?

If your widget integration encrypts PSE reference fields using JWE (for example, to comply with anti-fraud controls), the encrypted tokens generated with RSA produce ~790 bytes each. With three encrypted references, the URL can exceed safe length limits (~2,048 bytes) and the checkout may fail to load properly.

Symptom: The widget iframe stays blank or shows an error when using paymentMethod.referenceOne/Two/Three with long JWE values.

Not using JWE encryption?

If your reference fields are short (plain text), you probably don't need this transport. Your current integration works without changes. However, you can activate it preventively so your parameters are not exposed in the URL.

How do I activate it?

Add a single line to your widget configuration:

const checkout = new WidgetCheckout({
currency: 'COP',
amountInCents: 2449600,
reference: 'YOUR-REFERENCE',
publicKey: 'pub_prod_XXXXXXXXXX',
signature: { integrity: 'your-integrity-hash' },
paymentMethod: {
referenceOne: '<JWE-ENCRYPTED-TOKEN>',
referenceTwo: '<JWE-ENCRYPTED-TOKEN>',
referenceThree: '<JWE-ENCRYPTED-TOKEN>'
},
bootstrapTransport: 'postmessage' // ← this line
})

Possible values

ValueBehavior
Not declared (default)Everything works exactly as it does today. Configuration travels in the URL.
'query'Same as not declaring it. URL transport, current behavior.
'postmessage'The iframe URL is minimal (~90 bytes). Configuration is delivered via postMessage to the already-loaded checkout.

What changes for the end user?

Nothing. The payment experience is identical. The checkout behaves exactly the same: shows the loader, validates parameters, displays payment methods, processes the transaction, and returns the result to the merchant.

The only difference is internal: how data travels between your page and the checkout.

What changes for my integration?

AspectWithout bootstrapTransportWith bootstrapTransport: 'postmessage'
Widget configurationNo changesAdd one line
Result callbackNo changesNo changes
Integrity signature (signature)No changesNo changes
Available payment methodsNo changesNo changes
Backend transaction verificationNo changesNo changes
Iframe URLContains all config (~2,800+ bytes with JWE)Minimal (~90 bytes): only mode, bootstrap, and a nonce

Compatibility

  • Browsers: All browsers that support crypto.getRandomValues (Chrome 11+, Firefox 21+, Safari 6.1+, Edge 12+, IE 11). If the browser doesn't support it, the widget automatically falls back to URL transport and logs a warning in the console.
  • Widget versions: Only available starting from the version that includes this change. Previous versions of the widget.js script silently ignore the bootstrapTransport field.
  • Coexistence: Both transports are permanent. There is no plan to retire URL transport.

Security

ProtectionDescription
Cryptographic nonceEach widget opening generates a unique 128-bit nonce. Only the configuration that includes the correct nonce is accepted.
Origin validationThe checkout only accepts configuration from the parent window (your page). Messages from other origins are ignored.
Single useOnce the checkout receives the configuration, it ignores any subsequent messages. It is not possible to inject data mid-flow.
TimeoutIf the configuration doesn't arrive within 8 seconds, the checkout displays an error. It won't load indefinitely.
Explicit originThe widget sends the configuration only to the checkout's origin (never to '*'). Your JWE tokens are not exposed to other origins.

Full example

<script src="https://checkout.wompi.co/widget.js"></script>

<script>
const checkout = new WidgetCheckout({
currency: 'COP',
amountInCents: 5000000,
reference: 'ORDER-PSE-2024-001',
publicKey: 'pub_prod_XXXXXXXXXX',
redirectUrl: 'https://mystore.com/result',
signature: {
integrity: 'a1b2c3d4e5f6...'
},
paymentMethod: {
referenceOne: 'eyJhbGciOiJSU0EtT0FFUC0yNTYi...', // JWE encrypted
referenceTwo: 'eyJhbGciOiJSU0EtT0FFUC0yNTYi...',
referenceThree: 'eyJhbGciOiJSU0EtT0FFUC0yNTYi...'
},
bootstrapTransport: 'postmessage'
})

checkout.open(function (result) {
const transaction = result.transaction
console.log('Status:', transaction.status)
// Verify the transaction on your backend using transaction.id
})
</script>

Frequently asked questions

Can I use bootstrapTransport: 'postmessage' without encrypting the references?

Yes. It works with any configuration, not just JWE. If your query string is short you don't need it, but there's no problem using it all the time.

Does it work with the tokenization mode?

Yes. Both modes (purchase and tokenization) work with both transports.

Does it affect the integrity signature (signature.integrity)?

No. The signature is calculated over the same fields as always. The transport does not modify the data, only how it reaches the checkout.

What happens if I use an old version of widget.js?

The bootstrapTransport field is silently ignored. The widget continues to work with URL transport. No error or breakage occurs.

Does the backend endpoint receive different data?

No. The data format that is persisted is identical regardless of the transport. Transaction flows (PSE, BNPL, etc.) work without changes.

Flow diagram

postMessage flow between your page and Wompi Checkout

Technical notes

  • The 8-second timeout is fixed and not configurable by the merchant.
  • If crypto.getRandomValues is not available (very old browsers), the widget automatically falls back to URL transport and logs a warning in the console.
  • The bootstrapTransport field is not included in the persisted data. It is transport metadata, not transaction data.