The Hum widget provides a universal notification system that works across web browsers, iOS WebViews, and Android WebViews. All hooks use four communication methods for maximum compatibility:
postMessage - Works in web iframes, iOS WKWebView, and Android WebView
iOS WKWebView message handler - Direct communication with iOS native code
Android WebView interface - Direct communication with Android native code
Triggered when an order is successfully submitted or fails.
This widget event belongs to the widget checkout contract. Do not equate its success value with a Hum Broadband MCP fulfillment state. MCP states report recorded submission evidence and do not confirm ISP acceptance or installation.
Widget payloads use three currency key names: Money objects use currency, totalAmount uses amount_currency, and bundle_discounts entries use currency_iso. All three contain ISO 4217 currency codes such as "USD".
import com.google.gson.annotations.SerializedNamedata class OrderCompletedMessage( val type: String, val data: OrderCompletedData)data class OrderCompletedData( val orderId: String, val success: Boolean, val message: String?, val orderData: OrderData?, val timestamp: String)data class OrderData( val selectedPlan: SelectedPlan, val customerData: CustomerData, val totalAmount: TotalAmount?)data class SelectedPlan( val id: String, val name: String, val providerName: String, val pricing: Money)data class Money( @SerializedName("amount_cents") val amountCents: Int, val currency: String)data class CustomerData( val firstName: String, val lastName: String, val email: String, val phoneNumber: String)data class TotalAmount( @SerializedName("amount_cents") val amountCents: Int, @SerializedName("amount_currency") val amountCurrency: String)
2
Setup WebView with JavaScript Interface
import android.webkit.JavascriptInterfaceimport android.webkit.WebViewimport com.google.gson.Gsonclass WebViewActivity : AppCompatActivity() { private lateinit var webView: WebView private val gson = Gson() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_webview) webView = findViewById(R.id.webview) setupWebView() } private fun setupWebView() { webView.settings.apply { javaScriptEnabled = true domStorageEnabled = true } // Add JavaScript interface for widget communication webView.addJavascriptInterface( WebAppInterface(this), "AndroidInterface" ) // Load widget page webView.loadUrl("https://webview.letshum.com?apiKey=YOUR_API_KEY") } inner class WebAppInterface(private val context: WebViewActivity) { @JavascriptInterface fun onOrderCompleted(jsonMessage: String) { try { val message = gson.fromJson(jsonMessage, OrderCompletedMessage::class.java) runOnUiThread { handleOrderCompleted(message) } } catch (e: Exception) { Log.e("WebAppInterface", "Error parsing order completion message", e) } } }}
3
Handle Order Completion
private fun handleOrderCompleted(message: OrderCompletedMessage) { val data = message.data if (data.success) { Log.i("HumWidget", "✅ Order ${data.orderId} completed at ${data.timestamp}") // Access order data data.orderData?.let { orderData -> val customer = orderData.customerData val plan = orderData.selectedPlan Log.i("HumWidget", "Customer: ${customer.firstName} ${customer.lastName}") Log.i("HumWidget", "Email: ${customer.email}") Log.i("HumWidget", "Plan: ${plan.name} from ${plan.providerName}") orderData.totalAmount?.let { amount -> val dollars = amount.amountCents / 100.0 Log.i("HumWidget", "Total: $$dollars ${amount.amountCurrency}") } // Save to Room database, track in Firebase, update UI showOrderConfirmation(data.orderId, orderData) // Track in Firebase Analytics val bundle = Bundle().apply { putString("order_id", data.orderId) putString("provider", plan.providerName) putDouble("value", orderData.totalAmount?.amountCents?.div(100.0) ?: 0.0) } FirebaseAnalytics.getInstance(this) .logEvent("hum_order_completed", bundle) } } else { val errorMessage = data.message ?: "Unknown error" Log.e("HumWidget", "❌ Order failed: $errorMessage") showError(errorMessage) }}private fun showOrderConfirmation(orderId: String, orderData: OrderData) { AlertDialog.Builder(this) .setTitle("Order Completed") .setMessage( "Your order $orderId has been submitted successfully!\n\n" + "Plan: ${orderData.selectedPlan.name}\n" + "Provider: ${orderData.selectedPlan.providerName}" ) .setPositiveButton("OK") { dialog, _ -> dialog.dismiss() } .show()}
Always handle both success and failure cases in the order completion hook. Network issues or validation errors can cause order submission to fail.
interface SavePlanEventDetail { planId: string; // Unique plan identifier success: boolean; // Whether the save was successful message?: string; // Success/error message timestamp: string; // ISO timestamp of when the plan was saved}
data class PlanSavedMessage( val type: String, val data: PlanSavedData)data class PlanSavedData( val planId: String, val success: Boolean, val message: String?, val timestamp: String)inner class WebAppInterface(private val context: WebViewActivity) { @JavascriptInterface fun onPlanSaved(jsonMessage: String) { try { val message = gson.fromJson(jsonMessage, PlanSavedMessage::class.java) runOnUiThread { handlePlanSaved(message) } } catch (e: Exception) { Log.e("WebAppInterface", "Error parsing plan save message", e) } }}private fun handlePlanSaved(message: PlanSavedMessage) { val data = message.data if (data.success) { Log.i("HumWidget", "✅ Plan ${data.planId} saved at ${data.timestamp}") // Save to SharedPreferences or Room database savePlanToStorage(data.planId, data.timestamp) // Track in Firebase Analytics val bundle = Bundle().apply { putString("plan_id", data.planId) putString("timestamp", data.timestamp) } FirebaseAnalytics.getInstance(this) .logEvent("plan_saved", bundle) // Update UI showSaveConfirmation(data.planId) updateSavedPlansUI() } else { val errorMessage = data.message ?: "Unknown error" Log.e("HumWidget", "❌ Failed to save plan: $errorMessage") showError("Failed to save plan: $errorMessage") }}private fun savePlanToStorage(planId: String, timestamp: String) { val sharedPrefs = getSharedPreferences("HumWidget", Context.MODE_PRIVATE) val savedPlans = sharedPrefs.getStringSet("savedPlans", mutableSetOf())?.toMutableSet() ?: mutableSetOf() savedPlans.add(planId) sharedPrefs.edit() .putStringSet("savedPlans", savedPlans) .apply()}private fun showSaveConfirmation(planId: String) { Snackbar.make( findViewById(R.id.root), "Plan saved to your favorites!", Snackbar.LENGTH_SHORT ).show()}
interface UnsavePlanEventDetail { planId: string; // Unique plan identifier success: boolean; // Whether the unsave was successful message?: string; // Success/error message timestamp: string; // ISO timestamp of when the plan was unsaved}
For React Native applications using WebView:React Native messages arrive wrapped in an EMBEDDED_APP_EVENT envelope, with the widget event in its data property. Unlike React Native WebView messages, browser postMessage events deliver the widget event as { type, data } directly.
import { WebView } from 'react-native-webview';import AsyncStorage from '@react-native-async-storage/async-storage';function HumWidgetScreen() { const handleMessage = (event) => { try { const message = JSON.parse(event.nativeEvent.data); if (message.type !== 'EMBEDDED_APP_EVENT') return; const widgetEvent = message.data; switch (widgetEvent.type) { case 'humOrderCompleted': handleOrderCompleted(widgetEvent.data); break; case 'humPlanSaved': handlePlanSaved(widgetEvent.data); break; case 'humPlanUnsaved': handlePlanUnsaved(widgetEvent.data); break; } } catch (error) { console.error('Failed to parse widget message:', error); } }; const handleOrderCompleted = (data) => { const { orderId, success, orderData } = data; if (success) { console.log(`✅ Order ${orderId} completed`); // Navigate to confirmation screen navigation.navigate('OrderConfirmation', { orderId, orderData }); // Track in analytics analytics().logEvent('purchase', { transaction_id: orderId, value: orderData.totalAmount.amount_cents / 100, currency: orderData.totalAmount.amount_currency }); } else { Alert.alert('Order Failed', data.message); } }; const handlePlanSaved = async (data) => { if (data.success) { console.log(`✅ Plan ${data.planId} saved`); // Save to AsyncStorage const savedPlans = await AsyncStorage.getItem('savedPlans'); const plans = savedPlans ? JSON.parse(savedPlans) : []; plans.push(data.planId); await AsyncStorage.setItem('savedPlans', JSON.stringify(plans)); // Show toast Toast.show('Plan saved to favorites!'); } }; const handlePlanUnsaved = async (data) => { if (data.success) { console.log(`✅ Plan ${data.planId} unsaved`); // Remove from AsyncStorage const savedPlans = await AsyncStorage.getItem('savedPlans'); const plans = savedPlans ? JSON.parse(savedPlans) : []; const updated = plans.filter(id => id !== data.planId); await AsyncStorage.setItem('savedPlans', JSON.stringify(updated)); Toast.show('Plan removed from favorites'); } }; return ( <WebView source={{ uri: 'https://webview.letshum.com?apiKey=YOUR_API_KEY' }} onMessage={handleMessage} javaScriptEnabled={true} /> );}
React Native’s WebView component automatically handles postMessage communication between the web content and native code.
All hooks use the same four communication methods for universal compatibility across platforms. Choose the integration method that best fits your application architecture.