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.
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
| Value | Behavior |
|---|---|
| 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?
| Aspect | Without bootstrapTransport | With bootstrapTransport: 'postmessage' |
|---|---|---|
| Widget configuration | No changes | Add one line |
| Result callback | No changes | No changes |
Integrity signature (signature) | No changes | No changes |
| Available payment methods | No changes | No changes |
| Backend transaction verification | No changes | No changes |
| Iframe URL | Contains 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.jsscript silently ignore thebootstrapTransportfield. - Coexistence: Both transports are permanent. There is no plan to retire URL transport.
Security
| Protection | Description |
|---|---|
| Cryptographic nonce | Each widget opening generates a unique 128-bit nonce. Only the configuration that includes the correct nonce is accepted. |
| Origin validation | The checkout only accepts configuration from the parent window (your page). Messages from other origins are ignored. |
| Single use | Once the checkout receives the configuration, it ignores any subsequent messages. It is not possible to inject data mid-flow. |
| Timeout | If the configuration doesn't arrive within 8 seconds, the checkout displays an error. It won't load indefinitely. |
| Explicit origin | The 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
Technical notes
- The 8-second timeout is fixed and not configurable by the merchant.
- If
crypto.getRandomValuesis not available (very old browsers), the widget automatically falls back to URL transport and logs a warning in the console. - The
bootstrapTransportfield is not included in the persisted data. It is transport metadata, not transaction data.