Install the Uphold Payment Widget SDK and integrate it in web and native applications using sessions from the Widgets API and supported event handlers.
The Payment Widget runs against a session — a short-lived, server-side authorization scoped to one flow and one user. Create it server-side using your OAuth credentials.
Never call the Create Session endpoint from the client. Your Client Secret must not leave your backend.
Call POST /widgets/payment/sessions with the desired flow (select-for-deposit, select-for-withdrawal, or authorize) and the user the session is for:
Pass the entire response object to your frontend (e.g. as part of your page response or via your own API endpoint). The SDK takes it as a single session argument.
On the frontend, instantiate PaymentWidget with the session from Step 3, then mount it into a container element.
import { PaymentWidget } from '@uphold/enterprise-payment-widget-web-sdk';// session is the object returned by your backend in Step 3const widget = new PaymentWidget(session, { paymentMethods: [ { type: 'card' }, { type: 'bank' }, { type: 'crypto', assets: { include: ['BTC', 'ETH', 'XRP'] } } ], theme: { appearance: 'dark' }, // omit to follow system preference debug: true // verbose logging during development});widget.mountIframe(document.getElementById('payment-container'));
The container must have explicit CSS width and height — the iframe fills its bounds. Minimum recommended size is 400px × 600px.
For type inference on the complete event, pass the flow as a generic: new PaymentWidget<'select-for-deposit'>(session). See the SDK reference for full constructor details.
With your Sandbox credentials and the Sandbox Widget host configured, run through this checklist:
The Widget mounts and ready fires.
Selecting a payment method fires complete with the expected event.detail.value shape for your flow.
Closing or dismissing the Widget fires cancel.
The browser console shows no CSP violations (look for “Refused to frame”).
Once Sandbox is green, swap your OAuth credentials to Production. The Widget host is selected automatically by the session url returned from your backend — no client-side environment switching is needed.
Native mobile apps embed the Widget through a WebView that loads an HTML page hosting the SDK. Events flow between native code and the WebView through a JavaScript bridge.The setup is the same as the web flow above — install the SDK in your JS bundle, create the session on your backend, and instantiate PaymentWidget. The only addition is the bridge that forwards events to native code.
Bundle this HTML with your app and load it in the WebView. The sendToNativeApp helper at the bottom forwards events to whichever bridge is available (iOS, Android, or React Native).
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Payment Widget</title> <style> body { margin: 0; padding: 0; } #payment-container { width: 100%; height: 100vh; } </style> </head> <body> <div id="payment-container"></div> <!-- Include your JS bundle which contains the SDK --> <script src="your-bundle-with-sdk.js"></script> <script> window.addEventListener('load', () => { const session = createPaymentWidgetSession(); const widget = new PaymentWidget(session); widget.on('complete', (event) => { sendToNativeApp('complete', event.detail); widget.unmount(); }); widget.on('cancel', () => { sendToNativeApp('cancel'); widget.unmount(); }); widget.on('error', (event) => { sendToNativeApp('error', event.detail.error); widget.unmount(); }); widget.mountIframe(document.getElementById('payment-container')); }); function sendToNativeApp(type, data = null) { const message = { type, data }; if (window.webkit?.messageHandlers?.paymentWidgetMessage) { window.webkit.messageHandlers.paymentWidgetMessage.postMessage(message); } else if (window.PaymentBridge) { window.PaymentBridge.onMessage(JSON.stringify(message)); } else if (window.ReactNativeWebView) { window.ReactNativeWebView.postMessage(JSON.stringify(message)); } } </script> </body></html>
The SDK must be included in your WebView bundle (e.g. via your build pipeline). Loading it from a CDN is not supported.
Widget not displayingConfirm the Widget host for your environment is in the frame-src directive of your CSP (see Step 2). Open DevTools → Console and look for Refused to frame violations.Container is empty after mountThe iframe fills its container — the container must have explicit CSS width and height. Minimum recommended size is 400px × 600px.Events not firing in native appsVerify that:
JavaScript is enabled in the WebView.
The message bridge is registered before the HTML page loads.
Event handler names match the platform-specific bridge contract used in sendToNativeApp.
Widget never unmounts. The SDK does not auto-unmount. Call widget.unmount() from each terminal handler (complete, cancel, error).