Widget Integration Guide

Created by Erez Michaeli, Modified on Thu, 9 Oct at 5:08 AM by Erez Michaeli

Overview

This guide shows you how to embed your custom financial widget into your application using an iframe. Your widget is accessible via a single URL that handles data fetching, theming, and emits real-time events back to your application.

What you can do:

  • Embed the widget using a simple iframe

  • Configure language, theme, security identifiers, and user behavior

  • Listen to widget events (load, resize, user interactions, errors)

  • Handle security selections in your application (when enabled)

  • Auto-resize the widget based on content

Quick start

Basic iframe integration with auto-resize:

<iframe

  id="widget"

  src="https://embeded.bridgewise.com/en-US/TENANT_NAME/WIDGET_NAME?accessToken=YOUR_TOKEN&identifier=IDENTIFIER"

  style="width:100%; border:0; height:400px;"

>

</iframe>


<script>

  window.addEventListener("message", function (event) {

    if (event.origin !== "https://embeded.bridgewise.com") return;


    try {

      const message = JSON.parse(event.data);

      if (message.event && message.data && message.data.height) {

        document.getElementById("widget").style.height =

          message.data.height + "px";

      }

    } catch (e) {

      // Ignore invalid messages

    }

  });

</script>


URL structure

Base path uses path params and query params:

  • Path: /:language/:tenant/:widgetName

  • Query: ?accessToken=...&identifier=...&identifierType=...&mode=...&actionType=...&sessionId=...&userId=...

Example:

https://embeded.bridgewise.com/he-IL/YOUR_TENANT/stock-report-page?accessToken=YOUR_TOKEN&identifier=AAPL-NASDAQ&mode=lightoo&actionType=external


Important

  • Replace YOUR_TOKEN and AAPL_NASDAQ with your actual access token. 

  • To start integrating Bridgewise widgets, you’ll need an access token. Please follow the instructions in our Authentication Guide to generate your token.

Properties (configuration)

All configurable parameters accepted by the iframe. “Path” refers to the URL path segment; “Query” refers to the querystring.

Name

Location

Required

Default

Description

language

Path

No

en-US

UI language (BCP‑47 locale) passed to the widget.

tenant

Path

Yes

Your assigned tenant name

Tenant namespace provided to you.

widgetName

Path

Yes

stock-report-page | fund-report-page

Select which widget to render.

accessToken

Query

Yes

Bearer token used by widgets for data requests.

identifier

Query

Recommended

e.g. AAPL-Nasdaq24937

The security/fund identifier for data. Widgets generally need this to display content.

identifierType

Query

Conditional

ticker_exchange | ISIN

Identifier type for stock widgets. Use ticker_exchange for values like AAPL-Nasdaq.

mode

Query

No

light (default) | dark

Theme mode. Applies a dark theme when dark.

actionType

Query

No

default (default) | external | prevent

Controls click behavior on securities. See “Selection behavior” below.

sessionId

Query

No

Optional session identifier to correlate analytics.

userId

Query

yes

Optional user identifier to correlate analytics.

scoringMethod

Query

No

Optional scoring method to influence specific analytics, when supported.


Bridget Widget Specific Properties

The following properties are specific to Bridget widgets and only apply when using Bridget-related widgets:

Name

Location

Required

Allowed values / Default

Description

userName

Query

No

The name of the user for personalized interactions.

defaultMessage

Query

No

Default prompt message for Bridget chat interface.


Notes:

  • Stock widgets accept identifiers and may require identifierType depending on your identifier format.

  • Fund widgets accept identifiers and may require identifierType depending on your identifier format.

  • Bridget-specific properties only apply to Bridget widgets and are ignored by other widget types.


Selection behavior (actionType)

  • default: Selection will redirect to the report page on BridgeWise platform.

  • external: Widget emits on-security-select-<widgetName> to the host app; host should handle navigation.

  • prevent: Clicks are ignored for navigation and no selection event is emitted.


Exposed events (postMessage)

The iframe posts JSON-serialized messages to window.parent. Each message has the shape:

{ "event": "on-<name>-<widgetName>", "data": { ... } }


Events currently emitted:

  • Load/resize

    • on-load-<widgetName>{ height: number }

    • on-resize-<widgetName>{ height: number }

  • Interaction

    • on-click-<widgetName>{ id: string, ...widgetSpecific }

    • on-hover-<widgetName>{ id: string, ...widgetSpecific }

    • on-link-click-<widgetName>: { url: string, text?: string } (when onLinkClick callback is provided)

  • Selection (emitted only when actionType=external)

    • on-security-select-<widgetName>{ id: string, ticker?: string, exchange?: string, type: 'fund' | 'stock' }

  • Bridget Teaser

    • on-bridget-teaser-trigger-<widgetName>{ companyId: number | null, message: string, primaryTickerSymbol: string | null }

  • Status

    • on-success-<widgetName>{ height: number }

    • on-error-<widgetName>{ message: string, status: number, data?: unknown, height: number }

Example event handling:

window.addEventListener("message", function (event) {

  if (event.origin !== "https://embeded.bridgewise.com") return;


  const message = JSON.parse(event.data);

  console.log("Widget event:", message.event, message.data);

});


Handle user interactions (optional)

To capture when users click on securities in the widget:

window.addEventListener("message", function (event) {

  if (event.origin !== "https://embeded.bridgewise.com") return;


  try {

    const message = JSON.parse(event.data);


    // Auto-resize

    if (message.data && message.data.height) {

      document.getElementById("widget").style.height =

        message.data.height + "px";

    }


    // Handle security selection (requires actionType=external)

    if (message.event && message.event.includes("security-select")) {

      const { id, ticker, exchange } = message.data;

      console.log("User selected:", ticker, exchange);

      // Redirect to your security page or open modal

    }


    // Handle Bridget teaser trigger

    if (message.event && message.event.includes("bridget-teaser-trigger")) {

      const {

        companyId,

        message: teaserMessage,

        primaryTickerSymbol,

      } = message.data;

      console.log("Bridget teaser triggered:", {

        companyId,

        teaserMessage,

        primaryTickerSymbol,

      });

    // Handle Bridget teaser interaction (e.g., show chat modal, navigate to AI insights)

    },

    // Handle link clicks (when onLinkClick callback is provided)

    if (message.event && message.event.includes("link-click")) {

      const { url, text } = message.data;

      console.log("Link clicked:", { url, text });

      // Handle link click (e.g., open in new tab, navigate, or prevent default)

      // You can call your onLinkClick callback function here

      if (window.handleLinkClick) {

        window.handleLinkClick(url, text);

      }

    }

  } catch (e) {

    // Ignore invalid messages

  }

});


Theming and styling

  • Set mode=dark to enable dark theme; omit or set mode=light for light theme.

  • The host page should set iframe { width: 100%; border: 0; } and rely on resize events to set height.


Localization

  • Provide the language path param (e.g., en-UShe-IL). Widgets may internally fall back to a default language where needed.


Error handling

  • Invalid tenant or widget names render a friendly error screen inside the iframe.

  • Missing accessToken produces an error; listen for on-error-<widgetName> to surface it in the host app.


Security considerations

  • Always validate event.origin in your message listener to accept only your iframe host domain.

  • accessToken is passed via querystring; prefer short-lived tokens over long-lived ones.

iOS Integration (SwiftUI/UIKit)

For iOS apps, you can embed the widget using WKWebView with proper postMessage handling:

import SwiftUI

import WebKit


struct WidgetView: UIViewRepresentable {

    @Binding var height: CGFloat

    let widgetURL: String


    func makeCoordinator() -> Coordinator {

        Coordinator(self)

    }


    func makeUIView(context: Context) -> WKWebView {

        let configuration = WKWebViewConfiguration()

        let userContentController = WKUserContentController()


        // Set up message handler for postMessage bridge

        userContentController.add(context.coordinator, name: "postMessage")


        // JavaScript bridge to capture iframe postMessage events

        let script = """

            window.addEventListener('message', function(event) {

                if (event.origin !== 'https://embeded.bridgewise.com') return;

                window.webkit.messageHandlers.postMessage.postMessage(event.data);

            }, false);

        """


        let userScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)

        userContentController.addUserScript(userScript)

        configuration.userContentController = userContentController


        let webView = WKWebView(frame: .zero, configuration: configuration)

        webView.navigationDelegate = context.coordinator

        webView.scrollView.isScrollEnabled = false


        if let url = URL(string: widgetURL) {

            webView.load(URLRequest(url: url))

        }


        return webView

    }


    func updateUIView(_ webView: WKWebView, context: Context) {}


    class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {

        var parent: WidgetView


        init(_ parent: WidgetView) {

            self.parent = parent

        }


        func userContentController(_ userContentController: WKUserContentController,

                                 didReceive message: WKScriptMessage) {

            // Parse message from iframe

            var messageData: [String: Any]?


            if let jsonString = message.body as? String,

               let jsonData = jsonString.data(using: .utf8) {

                messageData = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any]

            } else if let directObject = message.body as? [String: Any] {

                messageData = directObject

            }


            guard let messageDict = messageData,

                  let event = messageDict["event"] as? String,

                  let data = messageDict["data"] as? [String: Any] else { return }


            // Handle resize events

            if event.contains("resize") || event.contains("load") || event.contains("success") {

                if let height = data["height"] as? NSNumber {

                    DispatchQueue.main.async {

                        self.parent.height = CGFloat(height.doubleValue)

                    }

                }

            }


            // Handle other events (security selection, errors, etc.)

            if event.contains("security-select") {

                // Handle security selection

                print("Security selected: \(data)")

            }

        }

    }

}


// Usage in SwiftUI

struct ContentView: View {

    @State private var widgetHeight: CGFloat = 400


    var body: some View {

        WidgetView(

            height: $widgetHeight,

            widgetURL: "https://embeded.bridgewise.com/en-US/YOUR_TENANT/stock-report-page?accessToken=YOUR_TOKEN&identifier=AAPL-Nasdaq&mode=light"

        )

        .frame(height: widgetHeight)

    }

}


Key points for iOS:

  • Use WKWebView with WKUserContentController for postMessage handling

  • Inject JavaScript to bridge iframe postMessage to webkit messageHandler

  • Handle height changes to dynamically resize the widget

  • Validate message origin for security

  • Disable WebView scrolling if you want the widget to control its own height

Android Integration (WebView)

For Android apps, use WebView with JavaScript interface to handle postMessage events:

// Widget WebView Component

class WidgetWebView @JvmOverloads constructor(

    context: Context,

    attrs: AttributeSet? = null

) : WebView(context, attrs) {


    private var heightChangeListener: ((Int) -> Unit)? = null

    private var eventListener: ((String, Map<String, Any>) -> Unit)? = null


    init {

        setupWebView()

    }


    @SuppressLint("SetJavaScriptEnabled")

    private fun setupWebView() {

        settings.apply {

            javaScriptEnabled = true

            domStorageEnabled = true

            loadWithOverviewMode = true

            useWideViewPort = true

        }


        // Add JavaScript interface for postMessage handling

        addJavascriptInterface(PostMessageHandler(), "AndroidInterface")


        webViewClient = object : WebViewClient() {

            override fun onPageFinished(view: WebView?, url: String?) {

                super.onPageFinished(view, url)


                // Inject JavaScript to capture iframe postMessage

                val script = """

                    window.addEventListener('message', function(event) {

                        if (event.origin !== 'https://embeded.bridgewise.com') return;


                        try {

                            var messageData = typeof event.data === 'string'

                                ? event.data

                                : JSON.stringify(event.data);

                            AndroidInterface.handlePostMessage(messageData);

                        } catch (e) {

                            console.error('Error handling postMessage:', e);

                        }

                    }, false);

                """


                evaluateJavascript(script, null)

            }

        }

    }


    fun setOnHeightChangeListener(listener: (Int) -> Unit) {

        heightChangeListener = listener

    }


    fun setOnEventListener(listener: (String, Map<String, Any>) -> Unit) {

        eventListener = listener

    }


    fun loadWidget(

        language: String = "en-US",

        tenant: String,

        widgetName: String,

        accessToken: String,

        identifier: String,

        mode: String = "light",

        actionType: String = "external"

    ) {

        val url = "https://embeded.bridgewise.com/$language/$tenant/$widgetName?" +

                "accessToken=$accessToken&identifier=$identifier&mode=$mode&actionType=$actionType"

        loadUrl(url)

    }


    inner class PostMessageHandler {

        @JavascriptInterface

        fun handlePostMessage(messageData: String) {

            try {

                val json = JSONObject(messageData)

                val event = json.getString("event")

                val data = json.getJSONObject("data")


                // Handle height changes

                if (event.contains("resize") || event.contains("load") || event.contains("success")) {

                    if (data.has("height")) {

                        val height = data.getInt("height")

                        post { heightChangeListener?.invoke(height) }

                    }

                }


                // Convert JSONObject to Map for event listener

                val dataMap = mutableMapOf<String, Any>()

                data.keys().forEach { key ->

                    dataMap[key] = data.get(key)

                }


                post { eventListener?.invoke(event, dataMap) }


            } catch (e: Exception) {

                e.printStackTrace()

            }

        }

    }

}


// Usage in Activity/Fragment

class MainActivity : AppCompatActivity() {

    private lateinit var widgetWebView: WidgetWebView


    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)


        widgetWebView = WidgetWebView(this)


        // Handle height changes

        widgetWebView.setOnHeightChangeListener { height ->

            val layoutParams = widgetWebView.layoutParams

            layoutParams.height = (height * resources.displayMetrics.density).toInt()

            widgetWebView.layoutParams = layoutParams

        }


        // Handle widget events

        widgetWebView.setOnEventListener { event, data ->

            when {

                event.contains("security-select") -> {

                    // Handle security selection

                    val ticker = data["ticker"] as? String

                    val exchange = data["exchange"] as? String

                    println("Security selected: $ticker-$exchange")

                }

                event.contains("error") -> {

                    // Handle errors

                    val message = data["message"] as? String

                    println("Widget error: $message")

                }

            }

        }


        // Load widget

        widgetWebView.loadWidget(

            tenant = "YOUR_TENANT",

            widgetName = "stock-report-page",

            accessToken = "YOUR_TOKEN",

            identifier = "AAPL-Nasdaq"

        )


        setContentView(widgetWebView)

    }

}


Key points for Android:

  • Enable JavaScript and DOM storage in WebView settings

  • Use @JavascriptInterface to create a bridge for postMessage handling

  • Inject JavaScript after page load to capture iframe messages

  • Convert pixel heights using display density for proper sizing

  • Handle events on the main thread using post()

  • Validate message origin in JavaScript for security

Mobile Integration Notes

For both iOS and Android:

  • The widget automatically handles responsive design for mobile viewports

  • Use actionType=external to handle security selections in your app instead of redirecting

  • Listen for on-error-* events to handle network or authentication issues gracefully

  • Consider implementing loading states while the widget initializes

  • Test with different device orientations and screen sizes

Security considerations:

  • Always validate the origin of postMessage events

  • Use HTTPS for all widget URLs

  • Store access tokens securely (iOS Keychain, Android EncryptedSharedPreferences)

  • Consider implementing token refresh logic for long-lived sessions

Notes

  • Event names are suffixed with the widgetName to let you distinguish multiple iframes on the same page.

  • Additional events like language fallbacks may be introduced; follow the on-*-<widgetName> convention.



Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article