Accepting Funds - PaidBy Payment Widget
Overview
This API doc explains how to integrate the PaidBy payment widget into a merchant website.
The payment widget is not opened with a hardcoded transaction ID. A merchant must first integrate with PaidBy API to create a payment transaction. PaidBy API returns a transaction_id & that transaction_id is then used to open a hosted payment widget.
High-level flow:
- Shopper selects product, service, deposit amount or checkout option on the merchant site.
- Merchant frontend sends checkout details to merchant backend.
- Merchant backend calls PaidBy API to create transaction.
- PaidBy API returns a
transaction_id. - Merchant frontend opens payment widget in a dialog.
- Merchant frontend loads widget URL using the returned
transaction_id. - Shopper completes a payment inside hosted PaidBy widget.
- Merchant confirms final payment status using backend status checks, callbacks or webhooks.
Integration architecture

Merchant backend should be responsible for calling PaidBy API.
Frontend should not directly create payment transactions if API credentials or sensitive merchant configuration are required.
The integration has 2 simple parts:
- Your backend creates a PaidBy payment transaction.
- Your frontend opens the PaidBy hosted payment widget using the returned
transaction_id.
The widget must not be opened with a hardcoded transaction ID. A new transaction should be created for a shopper’s checkout, deposit, or payment attempt.
Environment URLs
PaidBy provides separate widget URLs for stage/testing and production.
Use the stage URL while building and testing the integration. Use the production URL only when a merchant account has been approved for live traffic.
|
Environment |
Widget base URL |
|
stage |
|
|
production |
Stage widget URL format
https://paidby-stage.isxtech.com/landing/
Production widget URL format
https://paidby.isxtech.com/landing/
Do not use the stage widget URL in production.
Quick start
To open the PaidBy payment widget:
- Add a backend endpoint that creates a PaidBy transaction.
- Add the payment dialog HTML to your checkout page.
- Add the payment dialog CSS.
- Add the frontend JavaScript.
- Replace the placeholder values with your merchant configuration.
Step 1: Create a transaction using the PaidBy API
Before opening widget, a merchant must create a payment transaction through PaidBy API.
The exact PaidBy API endpoint, authentication method & request schema depends on merchant API integration. A typical request includes values such as:
Example merchant backend request
// Example only. Replace endpoint, authentication & request fields with PaidBy API specs for a merchant account. app.post('/api/create-paidby-transaction', async (req, res) => { const { amount, currency, merchantReference } = req.body; const response = await fetch('https://api.paidby.example.com/transactions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.PAIDBY_API_KEY}` }, body: JSON.stringify({ amount, currency, merchant_reference: merchantReference, parent_url: 'https://merchant.example.com' }) }); if (!response.ok) { return res.status(502).json({ error: 'Unable to create paidby transaction' }); } const data = await response.json(); res.json({ transaction_id: data.transaction_id }); });Frontend should receive only data it needs to open a widget, typically transaction_id.
Step 2: Add the payment dialog URL
Add this HTML to the merchant checkout page where a shopper clicks Proceed to pay.
<!DOCTYPE html> <html> <head> <title>page title</title> </head> <body> <iframe title="PaidBy Payment Widget" loading="lazy" scrolling="yes" allow="payment" allow="camera; microphone" src="about:blank"> </iframe> </body> </html>The iframe starts with: src="about:blank".
The iframe src should only be set after your backend successfully creates a transaction and returns a valid transaction_id.
Step 3: Add the payment dialog CSS
Add this CSS to your checkout page.
dialog#paidby-widget-dialog { padding: 0; border: none; border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.25); width: 100%; max-width: 1020px; } dialog#paidby-widget-dialog::backdrop { background: rgba(0,0,0,0.5); } dialog#paidby-widget-dialog iframe { width: 100%; height: 600px; border: none; display: block; } @media (max-width: 768px) { dialog#paidby-widget-dialog { margin: 0; border-radius: 0; width: 100vw; height: 100vh; max-width: 100vw; max-height: 100vh; } dialog#paidby-widget-dialog iframe { height: 100%; } }Step 4: Add the frontend JavaScript
Add this JavaScript to the checkout page.
var WIDGET_DIALOG_ID = "paidby-widget-dialog"; function ensureWidgetDialog() { var dlg = document.getElementById(WIDGET_DIALOG_ID); if (dlg) return dlg; dlg = document.createElement("dialog"); dlg.id = WIDGET_DIALOG_ID; document.body.appendChild(dlg); return dlg; } export function startPaymentWidget({ txId, webappBaseUrl, parentUrl, onEvent }) { var dlg = ensureWidgetDialog(); var url = webappBaseUrl + "/" + encodeURIComponent(txId) + "?parentUrl=" + encodeURIComponent(parentUrl); var existing = dlg.querySelector("iframe"); if (existing) { existing.src = url; } else { var f = document.createElement("iframe"); f.src = url; f.allow = "clipboard-write"; dlg.appendChild(f); } var iframe = dlg.querySelector("iframe"); function handleMessage(event) { if (event.source === iframe.contentWindow) { if (onEvent) onEvent(event.data); if (event.data && event.data.event === "close") { dlg.close(); } } } window.addEventListener("message", handleMessage); dlg.addEventListener("close", function () { window.removeEventListener("message", handleMessage); }, { once: true }); function handleBackdropClick(e) { if (e.target === dlg) { iframe.contentWindow.postMessage({ event: "close" }, "*"); dlg.close(); } } dlg.addEventListener("click", handleBackdropClick); dlg.addEventListener("close", function () { dlg.removeEventListener("click", handleBackdropClick); }, { once: true }); dlg.showModal(); }Step 5: Replace the example values
Before testing, replace the example values with your own merchant values.
|
Example value |
Replace with |
|
|
Your PaidBy transaction API endpoint. |
|
|
Your PaidBy API key or authentication method. |
|
|
Stage widget base URL. |
|
|
Production widget base URL. |
|
|
The shopper payment amount. |
|
|
The transaction currency. |
|
|
Your order, invoice, deposit, or checkout reference. |
|
|
The merchant site URL where the widget is opened. |
Dialog size
The payment widget should be displayed as a wide desktop-style payment panel.
Desktop
|
Property |
Recommended value |
|
Dialog width |
|
|
Maximum dialog width |
|
|
Widget height |
|
|
Minimum widget height |
|
|
Maximum dialog height |
|
|
Dialog border |
|
|
Dialog background |
Transparent around the iframe |
Mobile / small screens
|
Property |
Recommended value |
|
Dialog width |
|
|
Dialog padding |
|
|
Widget height |
|
|
Maximum widget height |
|
|
Minimum widget height |
|
|
Breakpoint |
|
A merchant site can style a dialog wrapper, backdrop, close button, sizing & iframe container.
A merchant site cannot directly style content inside a cross-origin iframe.
Backend & frontend
Merchant backend should:
- Authenticate with PaidBy API.
- Create transaction with correct amount, currency, merchant reference & shopper context.
- Return
transaction_idto merchant frontend. - Store merchant reference & PaidBy transaction reference for reconciliation.
- Verify final payment status server-side.
- Handle webhook notifications or payment status callbacks, if configured.
Merchant frontend should:
- Request transaction from merchant backend.
- Receive
transaction_id. - Build hosted widget URL.
- Open payment widget inside dialog.
- Display user-friendly loading & error states.
Widget URL format
The payment widget URL uses this format: /landing/?parentUrl=
Stage example
http://paidby-stage.isxtech.com/landing/00ebadf9-1fbd-4afa-b05e-21119ada1cb0?parentUrl=https://merchant.example.com
Production example
https://paidby.isxtech.com/landing/00ebadf9-1fbd-4afa-b05e-21119ada1cb0?parentUrl=https://merchant.example.com
URL values
|
Value |
Required |
Description |
|
|
Yes |
The PaidBy widget base URL for a selected environment. Use |
|
|
Yes |
The transaction identifier returned by the PaidBy API. This value is placed in the URL path. |
|
|
Yes |
The merchant site URL where the widget is opened. This should normally be the merchant origin, e.g., |
Parent URL
parentUrl must contain the URL of the merchant site where the widget is opened.
Recommended value: const merchantSiteUrl = window.location.origin;.
This produces values such as: https://merchant.example.com.
If the full page URL is required by your integration, use: const merchantSiteUrl = window.location.href;.
Do not use https://example.com in production. It should only be used for placeholder examples or local/demo testing.
Important implementation rules
- Create a new PaidBy transaction before opening the widget.
- Do not hardcode a transaction ID.
- Do not expose PaidBy API credentials in frontend code.
- Keep the iframe
srcasabout:blankuntil a validtransaction_idis returned. - Set the iframe
srconly after transaction creation succeeds. - URL-encode both
transaction_idandparentUrl.
- Disable the pay button while the transaction is being created.
- Show a clear error message if transaction creation fails.
- Verify the final payment status server-side before fulfilling goods or services.
- Use the stage widget URL only for testing.
- Use the production widget URL only for live traffic.
Accessibility requirements
Payment dialog should include:
role="dialog".aria-modal="true".allow="camera; microphone".- accessible title using
aria-labelledby. - close button with
aria-label="Close payment dialog". - escape-key support.
- focus placement when the dialog opens.
- background page scroll lock while the dialog is open.
Recommended behavior:
- Open dialog → focus close button.
- Close dialog → return focus to Proceed to pay button.
- Escape key → close dialog.
- Click backdrop → close dialog.
Cross-origin iframe styling
The PaidBy payment widget is loaded from a hosted external origin. A merchant site can style the:
- Dialog backdrop.
- Dialog size.
- iframe size.
- iframe wrapper.
- Close button.
- Merchant page around the widget.
A merchant site cannot directly style content inside a cross-origin iframe unless PaidBy exposes supported theme parameters or configuration options.
Recommended iframe attributes
<iframe title="PaidBy Payment Widget" loading="lazy" scrolling="yes" allow="payment" allow="camera; microphone" src="about:blank"> </iframe>Note that allow="camera; microphone" is required.
If your PaidBy integration requires clipboard access for sharing payment links, use allow="payment; clipboard-write".
Production checklist
Before going live, confirm the following:
- Merchant backend can authenticate with PaidBy API.
- Transaction is created server-side before opening a widget.
- A
transaction_idis valid & not expired. - Correct merchant
parentUrlis passed. - iframe is only loaded after a shopper initiates payment.
- Dialog works on desktop & mobile.
- Escape key & close button behavior are implemented.
- Merchant site handles payment success, failure, cancellation & timeout states.
- Webhooks or backend payment status checks are configured.
- Production domain allowlisting is configured, if required.
- Staging widget URL is replaced with production widget URL when going live.