> ## Documentation Index
> Fetch the complete documentation index at: https://docs.letshum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Widget Hook Notifications

> Learn how to listen for and handle events from the Hum widget across web, iOS, and Android platforms

# Hum Widget Hook Notifications

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:

1. **postMessage** - Works in web iframes, iOS WKWebView, and Android WebView
2. **iOS WKWebView message handler** - Direct communication with iOS native code
3. **Android WebView interface** - Direct communication with Android native code
4. **DOM events** - Fallback for same-document scenarios

## Available Hooks

The widget emits three types of notification hooks:

<CardGroup cols={3}>
  <Card title="Order Completion" icon="circle-check" href="#order-completion-hook">
    Triggered when an order is successfully submitted or fails
  </Card>

  <Card title="Plan Saved" icon="bookmark" href="#plan-save-hook">
    Triggered when a user saves an internet plan for later
  </Card>

  <Card title="Plan Unsaved" icon="bookmark-slash" href="#plan-unsave-hook">
    Triggered when a user removes a saved plan
  </Card>
</CardGroup>

## Order Completion Hook

Triggered when an order is successfully submitted or fails.

### Event Data Structure

```typescript theme={null}
interface OrderCompletedEventDetail {
  orderId: string;           // Unique order identifier
  success: boolean;          // Whether the order was successful
  message?: string;          // Success/error message
  orderData?: {              // Complete order information (only on success)
    selectedPlan: SelectedPlan;
    selectedInternetAddons: SelectedInternetAddon[];
    selectedTvProducts: SelectedTvProduct[];
    selectedTvAddons: SelectedTvProduct[];
    customerData: CustomerData;
    scheduleData: ScheduleData;
    totalAmount?: {
      amount_cents: number;
      amount_currency: string;
    };
  };
  timestamp: string;         // ISO timestamp of when the order was completed
}

interface Money {
  amount_cents: number;
  currency: string;
}

interface BundleDiscounts {
  [sku: string]: {
    cents: number;
    currency_iso: string;
  };
}

interface SelectedPlan {
  id: string;
  sku: string;
  name: string;
  providerId: string;
  providerName: string;
  providerLogo: string | null;
  pricing: Money;
  professionalInstallationFee?: Money | null;
  discounts?: {
    autopay_discount?: Money;
    paperless_billing_discount?: Money;
    combined_autopay_paperless_discount?: Money;
  };
  bundle_discounts?: BundleDiscounts;
  technology: string;
  downloadSpeed: number | null;
  uploadSpeed: number | null;
  description: string;
  quantity?: number;
  isIncluded?: boolean;
}

interface SelectedInternetAddon {
  id: string;
  sku: string;
  name: string;
  providerId: string;
  pricing: Money;
  description: string;
  category: string;
  max_quantity?: number;
  quantity?: number;
  initial_term_discount_months?: number;
  initial_term_discount?: Money;
  isIncluded?: boolean;
  select_type?: 'radio' | 'checkbox';
  bundle_discounts?: BundleDiscounts;
  info?: string;
}

interface SelectedTvProduct {
  id: string;
  sku: string;
  name: string;
  providerId: string;
  channelCount: number;
  streamingApps: string[];
  pricing: Money;
  description: string;
  max_quantity?: number;
  quantity?: number;
  initial_term_discount_months?: number;
  initial_term_discount?: Money;
  isIncluded?: boolean;
  select_type?: 'radio' | 'checkbox';
  bundle_discounts?: BundleDiscounts;
  info?: string;
}

interface CustomerData {
  firstName: string;
  lastName: string;
  phoneNumber: string;
  email: string;
  accountPin: string;
  dateOfBirth: string;
  dobMonth?: string;
  dobDay?: string;
  dobYear?: string;
  autoPay: boolean;
  paperlessBilling: boolean;
  mailingAddressSameAsService: boolean;
  mailingAddress: string;
  mailingAddressLine1: string;
  mailingAddressLine2: string;
  mailingCity: string;
  mailingState: string;
  mailingZipCode: string;
}

interface ScheduleData {
  desiredStartDate: string;
  installationType: 'self' | 'professional';
  firstInstallDate: string;
  firstInstallTime: string;
  secondInstallDate: string;
  secondInstallTime: string;
}
```

<Info>
  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"`.
</Info>

### Web Integration (JavaScript)

<Tabs>
  <Tab title="postMessage (Recommended)">
    ```javascript theme={null}
    window.addEventListener('message', (event) => {
      if (event.data.type === 'humOrderCompleted') {
        const { orderId, success, orderData, message, timestamp } = event.data.data;
        
        if (success) {
          console.log(`✅ Order ${orderId} completed at ${timestamp}`);
          console.log('Customer:', orderData.customerData);
          console.log('Selected Plan:', orderData.selectedPlan);
          console.log('Total Amount:', orderData.totalAmount);
          
          // Track conversion in Google Analytics
          gtag('event', 'purchase', {
            transaction_id: orderId,
            value: orderData.totalAmount.amount_cents / 100,
            currency: orderData.totalAmount.amount_currency
          });
          
          // Redirect to thank you page
          window.location.href = `/thank-you?orderId=${orderId}`;
        } else {
          console.error(`❌ Order failed: ${message}`);
          alert(`Order submission failed: ${message}`);
        }
      }
    });
    ```
  </Tab>

  <Tab title="DOM Event (Fallback)">
    ```javascript theme={null}
    document.addEventListener('humOrderCompleted', (event) => {
      const { orderId, success, orderData } = event.detail;
      
      if (success) {
        console.log('Order completed via DOM event:', { orderId, success });
        
        // Your custom logic here
        handleOrderSuccess(orderId, orderData);
      } else {
        handleOrderFailure(event.detail.message);
      }
    });
    ```
  </Tab>
</Tabs>

<Tip>
  Use the postMessage method as your primary integration method. It works across all platforms including iframes and WebViews.
</Tip>

### Common Use Cases

<AccordionGroup>
  <Accordion title="Track Conversion in Analytics">
    ```javascript theme={null}
    if (event.data.type === 'humOrderCompleted' && event.data.data.success) {
      const { orderId, orderData } = event.data.data;
      
      // Google Analytics 4
      gtag('event', 'purchase', {
        transaction_id: orderId,
        value: orderData.totalAmount.amount_cents / 100,
        currency: orderData.totalAmount.amount_currency,
        items: [{
          item_name: orderData.selectedPlan.name,
          item_category: 'Internet Service',
          price: orderData.selectedPlan.pricing.amount_cents / 100
        }]
      });
      
      // Facebook Pixel
      fbq('track', 'Purchase', {
        value: orderData.totalAmount.amount_cents / 100,
        currency: orderData.totalAmount.amount_currency
      });
    }
    ```
  </Accordion>

  <Accordion title="Send Data to Backend/CRM">
    ```javascript theme={null}
    if (event.data.type === 'humOrderCompleted' && event.data.data.success) {
      const { orderId, orderData } = event.data.data;
      
      // Send to your backend
      fetch('/api/orders', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          orderId,
          customer: orderData.customerData,
          plan: orderData.selectedPlan,
          timestamp: event.data.data.timestamp
        })
      })
      .then(response => response.json())
      .then(data => console.log('Order synced to backend:', data))
      .catch(error => console.error('Failed to sync order:', error));
    }
    ```
  </Accordion>

  <Accordion title="Update Application State">
    ```javascript theme={null}
    // React example
    if (event.data.type === 'humOrderCompleted' && event.data.data.success) {
      const { orderId, orderData } = event.data.data;
      
      // Update state
      setOrderStatus('completed');
      setOrderId(orderId);
      setCustomerData(orderData.customerData);
      
      // Navigate to confirmation page
      navigate(`/order-confirmation/${orderId}`);
    }
    ```
  </Accordion>
</AccordionGroup>

### iOS Integration (Swift)

<Steps>
  <Step title="Configure WKWebView with Message Handler">
    ```swift theme={null}
    import WebKit

    class WebViewController: UIViewController, WKScriptMessageHandler {
        var webView: WKWebView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // Configure message handler
            let contentController = WKUserContentController()
            contentController.add(self, name: "humWidget")
            
            let config = WKWebViewConfiguration()
            config.userContentController = contentController
            
            // Initialize WebView
            webView = WKWebView(frame: view.bounds, configuration: config)
            view.addSubview(webView)
            
            // Load widget page
            if let url = URL(string: "https://webview.letshum.com?apiKey=YOUR_API_KEY") {
                webView.load(URLRequest(url: url))
            }
        }
    }
    ```
  </Step>

  <Step title="Handle Widget Messages">
    ```swift theme={null}
    func userContentController(_ userContentController: WKUserContentController, 
                              didReceive message: WKScriptMessage) {
        guard message.name == "humWidget",
              let messageData = message.body as? [String: Any],
              let type = messageData["type"] as? String else {
            return
        }
        
        switch type {
        case "humOrderCompleted":
            handleOrderCompleted(messageData: messageData)
        default:
            break
        }
    }
    ```
  </Step>

  <Step title="Process Order Completion">
    ```swift theme={null}
    private func handleOrderCompleted(messageData: [String: Any]) {
        guard let data = messageData["data"] as? [String: Any],
              let orderId = data["orderId"] as? String,
              let success = data["success"] as? Bool else {
            return
        }
        
        if success {
            print("✅ Order \(orderId) completed successfully!")
            
            // Access order data
            if let orderData = data["orderData"] as? [String: Any] {
                let customerData = orderData["customerData"] as? [String: Any]
                let firstName = customerData?["firstName"] as? String
                let lastName = customerData?["lastName"] as? String
                let email = customerData?["email"] as? String
                
                print("Customer: \(firstName ?? "") \(lastName ?? "")")
                print("Email: \(email ?? "")")
                
                // Save to Core Data, update UI, track in analytics
                DispatchQueue.main.async {
                    self.showOrderConfirmation(orderId: orderId)
                }
            }
            
            if let totalAmount = (data["orderData"] as? [String: Any])?["totalAmount"] as? [String: Any] {
                let cents = totalAmount["amount_cents"] as? Int ?? 0
                let currency = totalAmount["amount_currency"] as? String ?? "USD"
                let dollars = Double(cents) / 100.0
                print("Total: \(dollars) \(currency)")
            }
        } else {
            let errorMessage = data["message"] as? String ?? "Unknown error"
            print("❌ Order failed: \(errorMessage)")
            
            DispatchQueue.main.async {
                self.showError(message: errorMessage)
            }
        }
    }

    private func showOrderConfirmation(orderId: String) {
        let alert = UIAlertController(
            title: "Order Completed",
            message: "Your order \(orderId) has been submitted successfully!",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    ```
  </Step>
</Steps>

### Android Integration (Kotlin)

<Steps>
  <Step title="Define Data Classes">
    ```kotlin theme={null}
    import com.google.gson.annotations.SerializedName

    data 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
    )
    ```
  </Step>

  <Step title="Setup WebView with JavaScript Interface">
    ```kotlin theme={null}
    import android.webkit.JavascriptInterface
    import android.webkit.WebView
    import com.google.gson.Gson

    class 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)
                }
            }
        }
    }
    ```
  </Step>

  <Step title="Handle Order Completion">
    ```kotlin theme={null}
    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()
    }
    ```
  </Step>
</Steps>

<Warning>
  Always handle both success and failure cases in the order completion hook. Network issues or validation errors can cause order submission to fail.
</Warning>

## Plan Save Hook

Triggered when a user saves an internet plan for later.

### Event Data Structure

```typescript theme={null}
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
}
```

### Web Integration (JavaScript)

<Tabs>
  <Tab title="postMessage (Recommended)">
    ```javascript theme={null}
    window.addEventListener('message', (event) => {
      if (event.data.type === 'humPlanSaved') {
        const { planId, success, message, timestamp } = event.data.data;
        
        if (success) {
          console.log(`✅ Plan ${planId} saved at ${timestamp}`);
          
          // Store in localStorage
          const savedPlans = JSON.parse(localStorage.getItem('savedPlans') || '[]');
          savedPlans.push({
            planId,
            savedAt: timestamp
          });
          localStorage.setItem('savedPlans', JSON.stringify(savedPlans));
          
          // Send to backend
          fetch('/api/saved-plans', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ planId, userId: getCurrentUserId() })
          });
          
          // Update UI
          showNotification('Plan saved successfully!', 'success');
        } else {
          console.error(`❌ Failed to save plan: ${message}`);
          showNotification(`Failed to save plan: ${message}`, 'error');
        }
      }
    });
    ```
  </Tab>

  <Tab title="DOM Event (Fallback)">
    ```javascript theme={null}
    document.addEventListener('humPlanSaved', (event) => {
      const { planId, success, message, timestamp } = event.detail;
      
      if (success) {
        console.log(`✅ Plan ${planId} saved at ${timestamp}`);
        
        // Store in localStorage
        const savedPlans = JSON.parse(localStorage.getItem('savedPlans') || '[]');
        savedPlans.push({
          planId,
          savedAt: timestamp
        });
        localStorage.setItem('savedPlans', JSON.stringify(savedPlans));
        
        // Your custom logic here
        handlePlanSaved(planId, timestamp);
      } else {
        handlePlanSaveFailure(message);
      }
    });
    ```
  </Tab>
</Tabs>

<Tip>
  Store saved plan IDs in your database or local storage to enable features like "My Saved Plans" or filtering widget results to show only saved plans.
</Tip>

### iOS Integration (Swift)

```swift theme={null}
if type == "humPlanSaved" {
    handlePlanSaved(messageData: messageData)
}

private func handlePlanSaved(messageData: [String: Any]) {
    guard let data = messageData["data"] as? [String: Any],
          let planId = data["planId"] as? String,
          let success = data["success"] as? Bool,
          let timestamp = data["timestamp"] as? String else {
        return
    }
    
    if success {
        print("✅ Plan \(planId) saved at \(timestamp)")
        
        // Save to Core Data or UserDefaults
        savePlanToStorage(planId: planId, timestamp: timestamp)
        
        // Track in analytics
        Analytics.logEvent("plan_saved", parameters: [
            "plan_id": planId,
            "timestamp": timestamp
        ])
        
        // Update UI
        DispatchQueue.main.async {
            self.showSaveConfirmation(planId: planId)
            self.updateSavedPlansUI()
        }
    } else {
        let errorMessage = data["message"] as? String ?? "Unknown error"
        print("❌ Failed to save plan: \(errorMessage)")
        
        DispatchQueue.main.async {
            self.showError(message: "Failed to save plan: \(errorMessage)")
        }
    }
}

private func savePlanToStorage(planId: String, timestamp: String) {
    var savedPlans = UserDefaults.standard.stringArray(forKey: "savedPlans") ?? []
    if !savedPlans.contains(planId) {
        savedPlans.append(planId)
        UserDefaults.standard.set(savedPlans, forKey: "savedPlans")
    }
}

private func showSaveConfirmation(planId: String) {
    let alert = UIAlertController(
        title: "Plan Saved",
        message: "Plan has been saved to your favorites!",
        preferredStyle: .alert
    )
    alert.addAction(UIAlertAction(title: "OK", style: .default))
    present(alert, animated: true)
}
```

### Android Integration (Kotlin)

```kotlin theme={null}
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()
}
```

## Plan Unsave Hook

Triggered when a user removes a saved plan.

### Event Data Structure

```typescript theme={null}
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
}
```

### Web Integration (JavaScript)

<Tabs>
  <Tab title="postMessage (Recommended)">
    ```javascript theme={null}
    window.addEventListener('message', (event) => {
      if (event.data.type === 'humPlanUnsaved') {
        const { planId, success, message, timestamp } = event.data.data;
        
        if (success) {
          console.log(`✅ Plan ${planId} unsaved at ${timestamp}`);
          
          // Remove from localStorage
          const savedPlans = JSON.parse(localStorage.getItem('savedPlans') || '[]');
          const updatedPlans = savedPlans.filter(p => p.planId !== planId);
          localStorage.setItem('savedPlans', JSON.stringify(updatedPlans));
          
          // Send to backend
          fetch(`/api/saved-plans/${planId}`, {
            method: 'DELETE',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ userId: getCurrentUserId() })
          });
          
          // Update UI
          showNotification('Plan removed from saved plans', 'info');
        } else {
          console.error(`❌ Failed to unsave plan: ${message}`);
          showNotification(`Failed to unsave plan: ${message}`, 'error');
        }
      }
    });
    ```
  </Tab>

  <Tab title="DOM Event (Fallback)">
    ```javascript theme={null}
    document.addEventListener('humPlanUnsaved', (event) => {
      const { planId, success, message, timestamp } = event.detail;
      
      if (success) {
        console.log(`✅ Plan ${planId} unsaved at ${timestamp}`);
        
        // Remove from localStorage
        const savedPlans = JSON.parse(localStorage.getItem('savedPlans') || '[]');
        const updatedPlans = savedPlans.filter(p => p.planId !== planId);
        localStorage.setItem('savedPlans', JSON.stringify(updatedPlans));
        
        // Your custom logic here
        handlePlanUnsaved(planId, timestamp);
      } else {
        handlePlanUnsaveFailure(message);
      }
    });
    ```
  </Tab>
</Tabs>

### iOS Integration (Swift)

```swift theme={null}
if type == "humPlanUnsaved" {
    handlePlanUnsaved(messageData: messageData)
}

private func handlePlanUnsaved(messageData: [String: Any]) {
    guard let data = messageData["data"] as? [String: Any],
          let planId = data["planId"] as? String,
          let success = data["success"] as? Bool else {
        return
    }
    
    if success {
        print("✅ Plan \(planId) unsaved")
        
        // Remove from storage
        removePlanFromStorage(planId: planId)
        
        // Track in analytics
        Analytics.logEvent("plan_unsaved", parameters: ["plan_id": planId])
        
        // Update UI
        DispatchQueue.main.async {
            self.updateSavedPlansUI()
            self.showNotification(message: "Plan removed from favorites")
        }
    }
}

private func removePlanFromStorage(planId: String) {
    var savedPlans = UserDefaults.standard.stringArray(forKey: "savedPlans") ?? []
    savedPlans.removeAll { $0 == planId }
    UserDefaults.standard.set(savedPlans, forKey: "savedPlans")
}
```

### Android Integration (Kotlin)

```kotlin theme={null}
@JavascriptInterface
fun onPlanUnsaved(jsonMessage: String) {
    try {
        val message = gson.fromJson(jsonMessage, PlanUnsavedMessage::class.java)
        
        runOnUiThread {
            handlePlanUnsaved(message)
        }
    } catch (e: Exception) {
        Log.e("WebAppInterface", "Error parsing plan unsave message", e)
    }
}

private fun handlePlanUnsaved(message: PlanUnsavedMessage) {
    val data = message.data
    
    if (data.success) {
        Log.i("HumWidget", "✅ Plan ${data.planId} unsaved")
        
        // Remove from storage
        removePlanFromStorage(data.planId)
        
        // Track in Firebase
        val bundle = Bundle().apply {
            putString("plan_id", data.planId)
        }
        FirebaseAnalytics.getInstance(this)
            .logEvent("plan_unsaved", bundle)
        
        // Update UI
        updateSavedPlansUI()
        showNotification("Plan removed from favorites")
    }
}

private fun removePlanFromStorage(planId: String) {
    val sharedPrefs = getSharedPreferences("HumWidget", Context.MODE_PRIVATE)
    val savedPlans = sharedPrefs.getStringSet("savedPlans", mutableSetOf())?.toMutableSet()
    
    savedPlans?.remove(planId)
    
    sharedPrefs.edit()
        .putStringSet("savedPlans", savedPlans)
        .apply()
}
```

## React Native Integration

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.

```javascript theme={null}
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}
    />
  );
}
```

<Info>
  React Native's WebView component automatically handles postMessage communication between the web content and native code.
</Info>

## Browser Compatibility

All hooks work in:

* ✅ Chrome, Firefox, Safari, Edge (all modern versions)
* ✅ iOS WKWebView (iOS 11+)
* ✅ Android WebView (Android 5.0+)
* ✅ React Native WebView
* ✅ Cordova/PhoneGap WebView

## Summary

<Note>
  All hooks use the same four communication methods for universal compatibility across platforms. Choose the integration method that best fits your application architecture.
</Note>

| Hook                | Event Type          | Trigger                          | Key Data                                       |
| ------------------- | ------------------- | -------------------------------- | ---------------------------------------------- |
| **Order Completed** | `humOrderCompleted` | Order submission success/failure | `orderId`, `success`, `orderData`, `timestamp` |
| **Plan Saved**      | `humPlanSaved`      | User saves a plan                | `planId`, `success`, `timestamp`               |
| **Plan Unsaved**    | `humPlanUnsaved`    | User removes saved plan          | `planId`, `success`, `timestamp`               |

## Next Steps

<CardGroup cols={2}>
  <Card title="JavaScript Integration" icon="code" href="/widget-integration/javascript">
    Step-by-step guide for embedding the widget with JavaScript
  </Card>

  <Card title="Configuration Options" icon="sliders" href="/widget-integration/overview#configuration-options">
    Explore all available widget configuration parameters
  </Card>

  <Card title="API Integration" icon="code" href="/api-integration">
    Build custom integrations using the Hum API
  </Card>

  <Card title="Get Support" icon="life-ring" href="mailto:support@letshum.com">
    Contact our team for integration assistance
  </Card>
</CardGroup>
