# API Integration Source: https://docs.letshum.com/api-integration Learn how to integrate Hum into your application using our API # Building with the Hum API The Hum API provides endpoints for discovering available internet service providers, plans, and pricing at addresses in the United States. With a single API call, you can retrieve available internet options for an address and immediately display them to your users. The partner API provides availability lookup and reporting. Residents place orders through the Hum widget checkout; the partner API does not provide an order-placement endpoint. The Hum API returns provider data immediately in the session creation response - no polling or additional requests required. ## Integration Overview Integrating with Hum is a simple three-step process: 1. **Authenticate** with your API key 2. **Create a session** with the service address 3. **Display results** from the immediate response ## Quick Start Guide Start in Sandbox with the examples below. To go live, switch the base URL to `https://api.letshum.com` and use your Production key; see [Environments and API Keys](/environments). See [Environments and API Keys](/environments) to request an API key and choose the matching environment. Make a POST request to create a session with the service address. The API returns all available providers immediately in the response. ```bash cURL theme={null} curl -X POST https://api-sandbox.letshum.com/sessions \ -H "Authorization: Bearer hum_sandbox_XXXXXXXXXXXXXXXXXXXXXXXXXX" \ -H "Content-Type: application/json" \ -d '{ "street1": "29090 Tiffany Drive E", "street2": "Apt 4B", "zip": "48034" }' ``` ```json Success Response theme={null} { "message": "Session created successfully.", "request_status": "ok", "data": [ { "provider_id": "130317", "provider_name": "Xfinity", "provider_icon": null, "provider_logo": "https://cdn.example.com/providers/xfinity-logo.png", "provider_promo": {}, "button_label": "xfinity.com", "telephone": "+18332981431", "url": "https://affiliate.example.com/xfinity?session=SESSION_TOKEN_PLACEHOLDER", "url_promo": {}, "min_plan_price": { "currency": "USD", "amount_cents": 4000 }, "offerings": [ { "technology": "Cable", "max_download_speed": 1200, "max_upload_speed": 35 } ], "product_catalog": [ { "category": "internet", "category_name": "Internet Service", "category_description": "High-speed internet access plans", "products": [ { "id": "003aa129-5b35-443e-8ea6-c3a33977b5ab", "sku": "130317-INT-CBL-02", "name": "300 Mbps", "category": "internet", "category_name": "Internet Service", "technology": "cable", "position": 2, "description": "Great for everyday working, streaming, and learning. Price guaranteed for 60 months. No contract required.", "select_type": "radio", "download_speed": "300", "upload_speed": "100", "data_limit": "Unlimited", "channel_count": 0, "streaming_apps": [], "is_required_to_checkout": true, "is_contract_required": false, "is_modem_router_included": true, "is_bundle_qualifier": false, "bundle_discounts": {}, "is_local_checkout": true, "required_with_plans": [], "included_with_plans": [], "initial_term_discount_months": 60, "second_term_discount_months": null, "only_available_with_plans": [], "hum_rank": 69, "pricing": { "extra_data_fee": { "amount_cents": 0, "currency": "USD" }, "professional_installation_fee": { "amount_cents": 5000, "currency": "USD" }, "self_installation_fee": { "amount_cents": 0, "currency": "USD" }, "activation_fee": { "amount_cents": 0, "currency": "USD" }, "initial_term_discount": { "amount_cents": 1500, "currency": "USD" }, "second_term_discount": { "amount_cents": 0, "currency": "USD" }, "third_term_discount": { "amount_cents": 0, "currency": "USD" }, "autopay_discount": { "amount_cents": 0, "currency": "USD" }, "paperless_billing_discount": { "amount_cents": 0, "currency": "USD" }, "combined_autopay_paperless_discount": { "amount_cents": 1000, "currency": "USD" }, "net_monthly_price": { "amount_cents": 5500, "currency": "USD" }, "gross_monthly_fee": { "amount_cents": 8000, "currency": "USD" } }, "max_quantity": 1, "product_promo": {}, "info": "5-year price lock guarantee" } ] } ] } ], "meta": { "session_token": "SESSION_TOKEN_PLACEHOLDER", "session_status": "open", "session_params": { "street1": "29090 Tiffany Dr E", "street2": "Apt 4B", "city": "Southfield", "state": "MI", "zip": "48034", "latitude": "42.50189", "longitude": "-83.29528", "campaign_id": null }, "service_address": "29090 Tiffany Dr E Apt 4B, Southfield, MI 48034-4540", "mdu": true, "agent_status": { "geocoding": "matched", "internet": "matched", "checkout": "pending" }, "created_at": "2026-07-13T12:26:15.618-04:00", "updated_at": "2026-07-13T12:26:15.618-04:00", "responded_at": "2026-07-13T16:26:16.278Z", "hum_data_set": "26011015" } } ``` Verify the response includes `request_status: "ok"` and provider data in the `data` array. The session creation response includes all available providers and their offerings. You can immediately display this information to your users: * Provider name and contact details * Available plans with speeds and pricing * Direct links to provider signup pages * Technology types (DSL, Cable, Fiber, etc.) Process the provider data immediately from the session creation response - no additional API calls needed. ## Optional: Retrieve Session Details Later If you need to retrieve session information later, you can use the session token: ```bash cURL theme={null} curl -X GET https://api-sandbox.letshum.com/sessions/YOUR_SESSION_TOKEN \ -H "Authorization: Bearer hum_sandbox_XXXXXXXXXXXXXXXXXXXXXXXXXX" ``` This endpoint returns session details and normalized address metadata. Its `data` object contains only the session token: ```json Session Details Response theme={null} { "message": "Session agents have successfully matched the service address. Please proceed.", "request_status": "ok", "data": { "session_token": "SESSION_TOKEN_PLACEHOLDER" }, "meta": { "session_token": "SESSION_TOKEN_PLACEHOLDER", "session_status": "open", "session_params": { "street1": "29090 Tiffany Dr E", "street2": "Apt 4B", "city": "Southfield", "state": "MI", "zip": "48034", "latitude": "42.50189", "longitude": "-83.29528", "campaign_id": null }, "service_address": "29090 Tiffany Dr E Apt 4B, Southfield, MI 48034-4540", "mdu": true, "agent_status": { "geocoding": "matched", "internet": "matched", "checkout": "pending" }, "created_at": "2026-07-13T12:26:15.618-04:00", "updated_at": "2026-07-13T12:26:15.618-04:00", "responded_at": "2026-07-13T16:26:16.900Z", "hum_data_set": "26011015" } } ``` While the session is open, retrieve its provider offerings again with `GET /sessions/{token}/services/internet`. See [Get Internet Service Availability](/api-reference/service-availability/get-internet-service-availability) in the API Reference. ## Session Lifecycle Sessions stay open until you close them with `DELETE /sessions/{token}`. Close a session when its lookup is finished; see [Close Session](/api-reference/sessions/close-session). ## Authentication All API requests require authentication using your API key. Include it in the Authorization header: ```bash theme={null} Authorization: Bearer YOUR_API_KEY ``` Use authenticated `GET /ping` as the safest first call before creating a session. It verifies that the selected environment accepts your key; see [Environments and API Keys](/environments#verify-your-key). ## Error Handling The API uses HTTP status codes to indicate success or failure. Standard validation, authentication, and session errors return this format: ```json Error Response Format theme={null} { "message": "Invalid session token.", "request_status": "warning" } ``` Validation responses may also include an `errors` object with field-specific details. Use the HTTP status code to determine whether a request succeeded. The `request_status` field is informational. Successful session metadata identifies the Hum data set with a numeric string such as `"26011015"`. Infrastructure responses such as HTTP 415, 429, and 500 use the formats documented in the API Reference. ### Common Error Scenarios **Common causes:** * Missing session parameters * Unpermitted session parameters * Invalid session token **Resolution:** Verify the request body uses supported session fields and confirm that the session token is correct. **Common causes:** * Missing or invalid API key * Expired API key * Invalid authentication header format **Resolution:** Verify your API key is correct and properly formatted in the Authorization header. **Common causes:** * Unsupported or incorrect request content type **Resolution:** Send session creation requests with `Content-Type: application/json`. **Common causes:** * Missing a valid address combination: `street1` + `zip`, `street1` + `city` + `state`, or `street1` + `city` + `zip` * Invalid address, state, or ZIP format ```json Example theme={null} { "message": "Session could not be created.", "request_status": "warning", "errors": { "base": [ "Provide either (street1 and zip), (street1, city, and state), or (street1, city, and zip)" ] } } ``` **Resolution:** Use the `errors` object to correct the address fields before retrying. Address validation is temporarily unavailable. ```json Response theme={null} { "address_validation_unavailable": true } ``` **Resolution:** Respect the `Retry-After` response header and retry later. **Common causes:** * Rate limit exceeded **Resolution:** Wait for the delay specified by the `Retry-After` header before retrying. **Common causes:** * Unexpected server-side failure **Resolution:** Retry the request after a brief delay. Contact support if the issue persists. For detailed error codes and handling, refer to the [API Reference](/api-reference/sessions/create-new-session) documentation. ## Best Practices Provide accurate address data upfront to ensure the best results. Include complete address details including street number, street name, city, state, and ZIP code. Use address validation services before sending requests to minimize errors and improve match rates. Implement robust error handling for common scenarios like invalid addresses, no service availability, or API rate limits. Always provide clear feedback to users when errors occur, using the error messages and status codes from the API response. Store and manage session tokens appropriately. Each session represents a unique address lookup, and tokens can be used to retrieve session details later. Consider caching provider data for frequently requested addresses to improve response times and reduce API calls. Process the immediate provider data returned in the session creation response to display options without delay. ## Example Integration Here's a complete example showing how to integrate the Hum API in different programming languages: ```javascript Node.js theme={null} async function findInternetProviders(address) { try { const response = await fetch('https://api-sandbox.letshum.com/sessions', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.HUM_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify(address) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); // Providers are immediately available in data.data const providers = data.data; const sessionToken = data.meta.session_token; console.log(`Found ${providers.length} providers at ${address.street1}`); return { providers, sessionToken, serviceAddress: data.meta.service_address }; } catch (error) { console.error('Error finding providers:', error); throw error; } } // Usage const address = { street1: "1001 Woodward Ave", city: "Detroit", state: "MI", zip: "48226" }; findInternetProviders(address) .then(result => { result.providers.forEach(provider => { console.log(`${provider.provider_name}: Starting at $${provider.min_plan_price.amount_cents / 100}/mo`); }); }) .catch(error => { console.error('Failed to find providers:', error); }); ``` ```python Python theme={null} import requests import os import json def find_internet_providers(address): try: response = requests.post( 'https://api-sandbox.letshum.com/sessions', headers={ 'Authorization': f'Bearer {os.getenv("HUM_API_KEY")}', 'Content-Type': 'application/json' }, json=address ) response.raise_for_status() data = response.json() # Providers are immediately available in data['data'] providers = data['data'] session_token = data['meta']['session_token'] print(f"Found {len(providers)} providers at {address['street1']}") return { 'providers': providers, 'session_token': session_token, 'service_address': data['meta']['service_address'] } except requests.exceptions.RequestException as error: print(f'Error finding providers: {error}') raise # Usage address = { 'street1': '1001 Woodward Ave', 'city': 'Detroit', 'state': 'MI', 'zip': '48226' } try: result = find_internet_providers(address) for provider in result['providers']: price = provider['min_plan_price']['amount_cents'] / 100 print(f"{provider['provider_name']}: Starting at ${price}/mo") except Exception as error: print(f'Failed to find providers: {error}') ``` ```php PHP theme={null} 'https://api-sandbox.letshum.com/sessions', CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json' ], CURLOPT_POSTFIELDS => json_encode($address) ]); $response = curl_exec($curl); $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); if ($httpCode !== 201) { throw new Exception("HTTP error! status: $httpCode"); } $data = json_decode($response, true); // Providers are immediately available in data['data'] $providers = $data['data']; $sessionToken = $data['meta']['session_token']; echo "Found " . count($providers) . " providers at " . $address['street1'] . "\n"; return [ 'providers' => $providers, 'sessionToken' => $sessionToken, 'serviceAddress' => $data['meta']['service_address'] ]; } // Usage $address = [ 'street1' => '1001 Woodward Ave', 'city' => 'Detroit', 'state' => 'MI', 'zip' => '48226' ]; try { $result = findInternetProviders($address); foreach ($result['providers'] as $provider) { $price = $provider['min_plan_price']['amount_cents'] / 100; echo $provider['provider_name'] . ": Starting at $" . $price . "/mo\n"; } } catch (Exception $error) { echo 'Failed to find providers: ' . $error->getMessage() . "\n"; } ?> ``` ## Next Steps Explore detailed endpoint documentation, parameter specifications, and response schemas. Import ready-made requests for Sandbox or Production. Try our no-code widget solution for quick integration without custom development. # Get Session Detail Source: https://docs.letshum.com/api-reference/analytics/get-session-detail /swagger.yaml get /analytics/sessions/{id} Returns a single session with address, order, commission, and full cart progression timeline. The session must belong to the authenticated client. # List Sessions Source: https://docs.letshum.com/api-reference/analytics/list-sessions /swagger.yaml get /analytics/sessions Returns a paginated list of sessions with address, cart progression, order, and commission data. Scoped to all tokens belonging to the authenticated client. ## Filtering - `start_date` / `end_date`: ISO 8601 date strings to bound the query window - `updated_since`: Return only sessions whose session or order data changed on or after this time; results are ordered by most recent change first - `token_ids[]`: Restrict to specific API tokens (must belong to your account) - `with_clicks`: When `true`, only returns sessions with click activity ## Pagination Results are paginated at 50 sessions per page. Use the `page` parameter and the `meta.pages` field to navigate. ## Syncing order status To keep a local copy of order status current, poll with `updated_since` set to the time of your last successful sync and page through all results. A session is returned whenever the session or its order changed, including installs, cancellations, and commission updates on sessions created long ago. Treat each returned row as authoritative and overwrite your stored copy. Do not use `start_date` as a sync cursor: it filters on session creation time, so status changes on previously synced sessions are never returned. Unrecognized parameters are ignored. # API Health Check Source: https://docs.letshum.com/api-reference/health-check/api-health-check /swagger.yaml get /ping Simple health check endpoint to verify API availability and authentication status. Use this endpoint for monitoring and to validate API tokens. ## Use Cases - Monitoring API health - Validating API tokens - Testing CORS configuration - Checking rate limits ## Response Times - Expected: < 100ms - Warning: > 500ms - Critical: > 1000ms ## Monitoring Guidelines - Poll every 60 seconds - Implement circuit breaker pattern - Track response times - Monitor error rates ## Example Response ```json { "message": "🎤 We're humming along!", "timestamp": "2026-07-13T16:26:17.874Z" } ``` # Get FCC Service Providers Source: https://docs.letshum.com/api-reference/informational/get-fcc-service-providers /swagger.yaml get /service_providers Retrieves a comprehensive list of FCC service providers and their supplemental brands. This endpoint provides access to the complete database of Internet service providers that are tracked by the FCC and used by Hum for service availability lookups. ## Use Cases - Building provider selection interfaces - Validating provider names and IDs - Understanding provider coverage and scale - Integration with external systems ## Data Structure The response includes both primary FCC providers and their supplemental brands: - **Primary Providers**: Main FCC-registered service providers - **Supplemental Brands**: Subsidiary brands and service lines under parent providers - **Total Residential Units**: Coverage metrics for each provider ## Provider Types - `fcc_provider`: Primary FCC-registered service provider - `supplemental_brand`: Supplemental brand that inherits coverage from parent provider - Supplemental brands are grouped immediately after their parent provider ## Response Format Providers are ordered by total residential units (largest first) with supplemental brands immediately following their parent provider in the list. ## Example Response ```json { "providers": [ { "id": "130403", "name": "T-Mobile", "fcc_name": "T-Mobile USA, Inc.", "total_residential_units": 97951103, "type": "fcc_provider" }, { "id": "130077", "name": "AT&T", "fcc_name": "AT&T Inc.", "total_residential_units": 80864481, "type": "fcc_provider" }, { "id": "130317", "name": "Xfinity", "fcc_name": "Comcast Corporation", "total_residential_units": 57287455, "type": "fcc_provider" }, { "id": "130317-1", "name": "Now XFinity", "fcc_name": "Comcast Corporation", "total_residential_units": 57287455, "type": "supplemental_brand" }, { "id": "131425", "name": "Verizon", "fcc_name": "Verizon Communications Inc.", "total_residential_units": 50554772, "type": "fcc_provider" } ] } ``` ## Important Notes - All requests require a valid bearer token - Response includes both active and inactive providers - Supplemental brands reference their parent provider's FCC name - Total residential units represent the parent provider's coverage # Get Internet Service Availability Source: https://docs.letshum.com/api-reference/service-availability/get-internet-service-availability /swagger.yaml get /sessions/{token}/services/internet Retrieves available Internet service providers and their offerings for the service address associated with the current session. ## Data Provided - Internet Service Providers (ISPs) - Available plans and pricing - Technology types (Fiber, Cable, DSL, etc.) - Maximum speeds - Provider contact information - Checkout URLs ## Data Freshness Provider and product data comes from the current Hum data set identified by `meta.hum_data_set`. ## Example Response ```json { "message": "Service coverage for 29090 TIFFANY DR E, SOUTHFIELD, MI 48034", "request_status": "ok", "data": [ { "provider_id": "130317", "provider_name": "Xfinity", "telephone": "+18332981431", "provider_icon": null, "provider_logo": "https://cdn.example.com/providers/xfinity-logo.png", "url": "https://affiliate.example.com/xfinity?session=SESSION_TOKEN", "button_label": "xfinity.com", "min_plan_price": { "amount_cents": 4000, "currency": "USD" }, "provider_promo": {}, "url_promo": {}, "offerings": [ { "technology": "Cable", "max_download_speed": 1200, "max_upload_speed": 35 } ], "product_catalog": [] } ], "meta": { "session_token": "SESSION_TOKEN_PLACEHOLDER", "session_status": "open", "session_params": { "street1": "29090 Tiffany Dr E", "street2": null, "city": "Southfield", "state": "MI", "zip": "48034", "latitude": "42.50189", "longitude": "-83.29528", "campaign_id": null }, "service_address": "29090 Tiffany Dr E, Southfield, MI 48034-4540", "mdu": false, "agent_status": { "geocoding": "matched", "internet": "matched", "checkout": "pending" }, "created_at": "2026-07-13T12:26:15.618-04:00", "updated_at": "2026-07-13T12:26:15.618-04:00", "responded_at": "2026-07-13T16:26:17.471Z", "hum_data_set": "26011015" } } ``` Note: Some providers may return an empty `product_catalog` array. # Close Session Source: https://docs.letshum.com/api-reference/sessions/close-session /swagger.yaml delete /sessions/{token} Closes an active session. Sessions remain open until this endpoint is called. ## Closing Behavior - The response returns the session envelope with `meta.session_status` set to `closed` - Subsequent operations on the closed session return HTTP 410 ## When to Close - After completing service lookup - When switching to a different address - After receiving final results - When abandoning a search ## Important Notes - A closed session cannot be reopened - Create a new session to perform another lookup # Create New Session Source: https://docs.letshum.com/api-reference/sessions/create-new-session /swagger.yaml post /sessions Creates a new session for processing a service address. This is typically the first endpoint called when starting a new address lookup flow. ## Request Flow 1. Submit service address components 2. Receive a session token 3. Use session token in the URL path for subsequent requests ## Processing During session creation, Hum: - Validates and normalizes the address - Geocodes the location - Identifies available service providers - Gathers plan and pricing information ## Response Handling - 201: Session created successfully with available provider results in `data` - 422: Invalid address components, check error details - 429: Rate limit exceeded; retry after the delay in `Retry-After` - 503: Address validation is temporarily unavailable; retry after the delay in `Retry-After` ## Important Notes - The session token is part of the URL path for all requests after creation - The session token is required for all subsequent requests - Sessions remain open until they are closed with DELETE - Rate limits apply to all requests ## Example Usage Provide address using one of: **(street1 and zip)**, **(street1, city, and state)**, or **(street1, city, and zip)**. State is optional when zip is present. ```json POST /sessions { "street1": "29090 Tiffany Drive E", "zip": "48034" } ``` Or with city and state, or street+city+zip (no state): ```json POST /sessions { "street1": "29090 Tiffany Drive E", "city": "Southfield", "state": "MI", "zip": "48034" } ``` # Get Session Status Source: https://docs.letshum.com/api-reference/sessions/get-session-status /swagger.yaml get /sessions/{token} Retrieves an open session. Session creation is synchronous, so provider results are returned by `POST /sessions`; this endpoint does not need to be polled for processing completion. ## Status Codes - 200: Session is open - 400: Session token is invalid - 410: Session is closed ## Response Data - `data.session_token`: The session token only - `meta.session_params`: Normalized address values - `meta.service_address`: Formatted service address - `meta.agent_status`: Agent status values ## Important Notes - The session token is part of the URL path for all requests after creation - Sessions remain open until they are closed with DELETE ## Example Response ```json { "message": "Session agents have successfully matched the service address. Please proceed.", "request_status": "ok", "data": { "session_token": "SESSION_TOKEN_PLACEHOLDER" }, "meta": { "session_token": "SESSION_TOKEN_PLACEHOLDER", "session_status": "open", "session_params": { "street1": "29090 Tiffany Dr E", "street2": "Apt 4B", "city": "Southfield", "state": "MI", "zip": "48034", "latitude": "42.50189", "longitude": "-83.29528", "campaign_id": null }, "service_address": "29090 Tiffany Dr E Apt 4B, Southfield, MI 48034-4540", "mdu": true, "agent_status": { "geocoding": "matched", "internet": "matched", "checkout": "pending" }, "created_at": "2026-07-13T12:26:15.618-04:00", "updated_at": "2026-07-13T12:26:15.618-04:00", "responded_at": "2026-07-13T16:26:16.900Z", "hum_data_set": "26011015" } } ``` # Environments and API Keys Source: https://docs.letshum.com/environments Choose a Hum API environment, obtain the matching key, and verify access # Environments and API Keys Hum provides separate Sandbox and Production environments with the same API surface. | Environment | Base URL | Purpose | | ----------- | --------------------------------- | ----------------------------------- | | Sandbox | `https://api-sandbox.letshum.com` | Integration development and testing | | Production | `https://api.letshum.com` | Live traffic | Build and test your integration against Sandbox before switching to Production. Sandbox serves the same national provider data set as Production, including the same data-set version. Use any real US address for testing; the example addresses in these docs work in Sandbox. An empty result means that no providers cover the address, just as it does in Production. ## Get an API Key API keys are issued by Hum for each environment. There is no self-serve key dashboard. Request keys from your Hum contact or email [support@letshum.com](mailto:support@letshum.com). Keys are environment-specific and visibly prefixed: * Sandbox: `hum_sandbox_XXXXXXXXXXXXXXXXXXXXXXXXXX` * Production: `hum_XXXXXXXXXXXXXXXXXXXXXXXXXX` A key works only in the environment that issued it. A Sandbox key cannot authenticate against Production, and a Production key cannot authenticate against Sandbox. ## Widget Environments Use the bundle and key for the same environment: | Environment | Widget bundle | Required key | | ----------- | ------------------------------------------- | ----------------------------------------- | | Production | `https://cdn.letshum.com/widget.js` | Production key beginning with `hum_` | | Sandbox | `https://cdn.letshum.com/widget-sandbox.js` | Sandbox key beginning with `hum_sandbox_` | The hosted WebView page at `https://webview.letshum.com` loads the Production widget bundle, so it requires a Production key. ## Verify Your Key Use `GET /ping` as the first authenticated request: ```bash cURL theme={null} curl https://api-sandbox.letshum.com/ping \ -H "Authorization: Bearer hum_sandbox_XXXXXXXXXXXXXXXXXXXXXXXXXX" ``` ```json Response theme={null} { "message": "🎤 We're humming along!", "timestamp": "2026-07-13T16:26:17.874Z" } ``` After this succeeds, continue with [API Integration](/api-integration). ## Postman Prefer Postman? Import the ready-made collection. See [Postman Collection](/postman). ## API Reference Playground The API Reference **Try it** playground defaults to the Sandbox server because Sandbox is listed first in the API specification. If you use a Production key without changing the server selector to Production, the request returns HTTP 401. # Introduction Source: https://docs.letshum.com/introduction Welcome to Hum — Connecting Residents with Internet Services # The Hum Platform Hum makes it simple to connect residents with the internet services they need. Whether you're building a property management platform, a real estate application, or any resident-focused service, Hum helps you provide a seamless internet setup experience. The partner API provides availability lookup and reporting. Residents place orders through the Hum widget checkout; the partner API does not provide an order-placement endpoint. Hum platform diagram: internet tasks (serviceability lookups, plan and pricing data, promos and contract terms, ordering and provisioning, account setup and billing, install scheduling) and network types (cable, fiber, fixed wireless, satellite) across 200+ providers flow through one Hum API, delivered to your app via API or widget Hum platform diagram: internet tasks (serviceability lookups, plan and pricing data, promos and contract terms, ordering and provisioning, account setup and billing, install scheduling) and network types (cable, fiber, fixed wireless, satellite) across 200+ providers flow through one Hum API, delivered to your app via API or widget ## What Hum Does Moving into a new home comes with many tasks - setting up internet shouldn't be one of them. Hum eliminates the complexity and gets residents connected faster. Hum streamlines the entire internet setup process by: * Finding available internet services at any address * Presenting clear, actionable service options * Simplifying the signup process * Getting residents connected faster ## How It Works Behind the scenes, Hum uses intelligent automation to: * Validate and standardize addresses for accurate service matching * Find available internet service providers and their offerings * Present up-to-date plans and pricing * Guide residents through a smooth onboarding process You can integrate these capabilities through our API or drop-in widget, making it easy to add internet service functionality wherever your residents need it. ## Integration Options Choose the integration method that works best for your use case: Get an API key and choose Sandbox or Production before you begin. Add a ready-to-use interface with just a few lines of code. Ideal for quick implementation with minimal development effort. Build a custom experience using our comprehensive API. Perfect for developers who need full control over the user interface and workflow. ## Next Steps Ready to get started? Follow these steps to begin integrating Hum: 1. [Get an API key and choose your environment](/environments) 2. Choose your preferred integration method from the options above 3. Start connecting your residents with the internet services they need Once you complete these steps, you'll have everything needed to provide seamless internet service connections for your residents. # Order & Commission Reporting Source: https://docs.letshum.com/order-reporting Retrieve order status, installation state, commissions, and checkout progression # Order & Commission Reporting The analytics endpoints provide partner-scoped session, order, commission, and cart-progression data. Analytics requests are limited to 60 requests per minute per IP. ## List Sessions `GET /analytics/sessions` returns 50 sessions per page. Use the `page` parameter and `meta.pages` to traverse pages; `meta.count` is the total number of matching sessions. Available filters: * `start_date` and `end_date`: Limit sessions by creation time. * `updated_since`: Return sessions whose session or order changed on or after the supplied time, ordered by most recent change first. * `token_ids[]`: Restrict results to API tokens owned by your account. A token you do not own returns HTTP 403. * `with_clicks`: When `true`, return only sessions with click activity. ```bash cURL theme={null} curl --get https://api-sandbox.letshum.com/analytics/sessions \ -H "Authorization: Bearer hum_sandbox_XXXXXXXXXXXXXXXXXXXXXXXXXX" \ --data-urlencode "updated_since=2026-07-13T12:00:00Z" \ --data-urlencode "page=1" ``` See [List Sessions](/api-reference/analytics/list-sessions) for the response schema and all parameters. ## Get Session Detail `GET /analytics/sessions/{id}` returns one session and adds: * `order`: The associated order, or `null` when no order was placed. * `cart_progression`: The full checkout-step timeline ordered by timestamp. See [Get Session Detail](/api-reference/analytics/get-session-detail). ## Move-in vs Move-out Traffic Every session returned by both analytics endpoints carries a `context` field of either `move_in` or `move_out`. Context is set by the API token that created the session, not by a request parameter. If you run both flows, you are issued a separate token for each, and each session inherits the context of the token behind it. Sessions created before this field was introduced return `move_in`. * `move_in`: a resident setting up service at a home they are moving into. * `move_out`: a resident leaving your property and setting up service at their next home. There is no `context` filter parameter. To report on one context, either request that token's traffic with `token_ids[]`, or group client-side on the `context` field. A move-out session's address is the resident's **destination** home, not your building. Do not roll move-out addresses up into property-level or building-level reporting. ## Order Fields The order object includes: * `order_number`: Unique order identifier. * `status`: `draft`, `submitted`, `processing`, `confirmed`, `complete`, or `cancelled`. * `ordered_at`: When the order was submitted. * `installed`: Whether service has been installed. * `installed_at`: Installation timestamp, or `null` when not installed. * `commission_cents`: Locked commission amount in cents, or `null` when no commission is recorded. The normal progression is `draft` → `submitted` → `processing` → `confirmed` → `complete`. `cancelled` is final. For cancelled orders, `installed` is always `false`, while `installed_at` and `commission_cents` are `null`. ## Reconciling Widget Orders The `orderId` in the widget's `humOrderCompleted` event is the same value as `order_number` in the analytics endpoints. Use it to join widget conversions to order and commission rows. The session's `campaign_id` provides session-level attribution. ## Keep a Local Copy Current The API does not send server-side webhooks for analytics changes. Poll with `updated_since` set to the time of your last successful sync and page through all results. A session is returned whenever the session or its order changed, including installs, cancellations, and commission updates on older sessions. Treat each returned row as authoritative and overwrite your stored copy. Do not use `start_date` as a sync cursor: it filters by session creation time, so later status changes to older sessions do not appear. ```text Sync loop theme={null} cursor = last_successful_sync_time repeat: sync_started_at = current_time() page = 1 do: response = GET /analytics/sessions?updated_since=cursor&page=page overwrite_local_rows(response.data) page = page + 1 while page <= response.meta.pages cursor = sync_started_at save_last_successful_sync_time(cursor) ``` # Postman Collection Source: https://docs.letshum.com/postman Import the Hum API collection and environments into Postman # Postman Collection The Hum API collection contains eight requests organized into Sessions, Analytics, and Utilities folders. It uses collection-level bearer authentication through the `api_key` variable. When **Create Session** succeeds, its test script stores the returned token in the collection's `session_token` variable. **Get Session Status**, **Get Internet Services**, and **Close Session** then use that token automatically, without copy-pasting it between requests. ## Download * [Hum API collection](/postman/hum-api.postman_collection.json) * [Sandbox environment](/postman/hum-sandbox.postman_environment.json) * [Production environment](/postman/hum-production.postman_environment.json) ## Import and Run 1. Import the collection and one environment file into Postman. 2. Select the imported environment. 3. Paste the key for that environment into the `api_key` variable. See [Environments and API Keys](/environments) to request a key. 4. Run **Health Check** first to verify the environment and key. As an alternative to the ready-made collection, import the [OpenAPI specification](/swagger.yaml) directly into Postman. # Widget Hook Notifications Source: https://docs.letshum.com/widget-integration/hooks 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: Triggered when an order is successfully submitted or fails Triggered when a user saves an internet plan for later Triggered when a user removes a saved plan ## 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; } ``` 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"`. ### Web Integration (JavaScript) ```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}`); } } }); ``` ```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); } }); ``` Use the postMessage method as your primary integration method. It works across all platforms including iframes and WebViews. ### Common Use Cases ```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 }); } ``` ```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)); } ``` ```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}`); } ``` ### iOS Integration (Swift) ```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)) } } } ``` ```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 } } ``` ```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) } ``` ### Android Integration (Kotlin) ```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 ) ``` ```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) } } } } ``` ```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() } ``` Always handle both success and failure cases in the order completion hook. Network issues or validation errors can cause order submission to fail. ## 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) ```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'); } } }); ``` ```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); } }); ``` 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. ### 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) ```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'); } } }); ``` ```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); } }); ``` ### 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 ( ); } ``` React Native's WebView component automatically handles postMessage communication between the web content and native code. ## 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 All hooks use the same four communication methods for universal compatibility across platforms. Choose the integration method that best fits your application architecture. | 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 Step-by-step guide for embedding the widget with JavaScript Explore all available widget configuration parameters Build custom integrations using the Hum API Contact our team for integration assistance # JavaScript Integration Source: https://docs.letshum.com/widget-integration/javascript Step-by-step guide to integrate the Hum widget into your website using JavaScript and HTML # JavaScript Widget Integration The Hum widget can be embedded directly into your website with just a few lines of JavaScript and HTML. This integration method is ideal for web applications, landing pages, and any environment where you have control over the HTML and JavaScript. This guide covers the JavaScript/HTML embed integration. For mobile app WebView integration, see [WebView Integration](/widget-integration/webview). To test with a Sandbox key, load the Sandbox widget bundle instead of the Production bundle shown below. See [Environments and API Keys](/environments#widget-environments). ## Quick Start Get up and running with the Hum widget in three simple steps: Add a container div where you want the widget to render: ```html theme={null}
``` You can customize the container ID, but make sure it matches the element you pass to the widget constructor.
Load the Hum widget script in your HTML page: ```html theme={null} ``` The script loads asynchronously and is hosted on a CDN for optimal performance. Initialize the widget with your API key: ```javascript theme={null} const hum = new HUM( 'YOUR_API_KEY_HERE', document.getElementById('hum-widget') ); hum.initialize(); // Store the instance globally for access by other components window.humInstance = hum; ``` Verify the widget loads by checking your browser's developer console for any error messages.
## Configuration The Hum widget accepts an optional configuration object as the third parameter. For a complete list of available options, see the [Widget Overview](/widget-integration/overview#configuration-options). ### Basic Configuration Example ```javascript theme={null} const hum = new HUM( 'YOUR_API_KEY_HERE', document.getElementById('hum-widget'), { resultLayout: 'checkout', primaryColor: '#1274f9', campaignId: 'Q4-2024-campaign' } ); hum.initialize(); ``` ### Advanced Configuration Example Here's an example with all available options: ```javascript theme={null} const hum = new HUM( 'YOUR_API_KEY', document.getElementById('hum-widget'), { // Display & Layout resultLayout: 'checkout', primaryColor: '#1274f9', showSavePlanButton: true, // Enable save/unsave plan button showAddressCompletionForm: false, // Let the widget collect the address itself // Tracking & Analytics campaignId: 'Q4-2024-landing-page', // Filtering & Limiting limitProviders: ['130077', '130317'], primaryProviders: ['130077'], limitTechnologies: ['Fiber', 'Cable', 'Wireless'], // Customer Data Pre-population customerData: { firstName: 'Jane', lastName: 'Smith', email: 'jane.smith@example.com', phoneNumber: '555-987-6543' } } ); hum.initialize(); ``` ## Integration Examples ### Example 1: Form-Based Integration Integrate the widget with an HTML form for address input: A `campaign_id` field inside widget address data is ignored. Set campaign attribution with the widget's `campaignId` configuration option. ```html index.html theme={null} Hum Widget Demo
``` Test your integration by entering a valid US address and verifying that service options load correctly. ### Example 2: Direct Address Loading Load address data directly when you already have the information: ```html direct-integration.html theme={null} Hum Widget - Direct Integration
``` Use direct integration when you already have address data from a previous form, user profile, or application state. ### Example 3: React Integration Integrate the Hum widget into a React application: ```javascript HumWidget.jsx theme={null} import React, { useEffect, useRef, useState } from 'react'; function HumWidget({ apiKey, addressData, config }) { const containerRef = useRef(null); const humInstanceRef = useRef(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { // Initialize widget const initializeWidget = async () => { try { setIsLoading(true); setError(null); // Create HUM instance const hum = new window.HUM( apiKey, containerRef.current, config ); await hum.initialize(); humInstanceRef.current = hum; // Load address if provided if (addressData) { await hum.internetServiceFromAddress(addressData); } setIsLoading(false); } catch (err) { console.error('Failed to initialize Hum widget:', err); setError(err.message); setIsLoading(false); } }; if (containerRef.current) { initializeWidget(); } // Cleanup return () => { if (humInstanceRef.current) { // Cleanup logic if needed humInstanceRef.current = null; } }; }, [apiKey, addressData, config]); // Method to update address const updateAddress = async (newAddress) => { if (humInstanceRef.current) { try { await humInstanceRef.current.internetServiceFromAddress(newAddress); } catch (err) { console.error('Failed to update address:', err); setError(err.message); } } }; if (error) { return (
Error loading widget: {error}
); } return (
{isLoading &&
Loading widget...
}
); } // Usage function App() { const addressData = { street1: "1001 Woodward Ave", city: "Detroit", state: "MI", zip: "48226" }; const config = { resultLayout: 'checkout', primaryColor: '#1274f9', campaignId: 'react-app' }; return (

Internet Service Finder

); } export default App; ``` ### Example 4: Vue.js Integration Integrate the widget into a Vue.js application: ```vue HumWidget.vue theme={null} ``` ## Address Data Format The widget expects address data in a specific JSON format: ```json theme={null} { "street1": "1001 Woodward Ave", "street2": "Suite 500", "city": "Detroit", "state": "MI", "zip": "48226", "latitude": 42.3317, "longitude": -83.0479 } ``` For complete field specifications, see the [Address Data Format](/widget-integration/overview#address-data-format) section in the overview. ## Address Validation The widget validates addresses using these rules: * **street1**: Primary address (street number and name) * **zip**: 5 or 9-digit ZIP code format (12345 or 12345-6789) * **street2**: Unit/apartment information (no specific format required) * **city**: City name (recommended for improved address match quality) * **state**: 2-letter US state code (recommended for improved address match quality) * **latitude/longitude**: Must be provided together and within US territorial bounds * Address must be geocodable by our system * Address must be serviceable by at least one provider * Coordinates must be within the bounds of the US and its territories ## Troubleshooting **Check these common issues:** * Verify the container div exists on the page * Ensure the widget script loads before your initialization code * Check that your API key is valid and properly formatted * Look for JavaScript errors in the browser console **Expected behavior:** The widget should initialize within 2-3 seconds of calling `hum.initialize()`. **Common address issues:** * Ensure the required fields (`street1` and `zip`) are provided * Include `city` and `state` when available to improve address match quality * Verify the state is a valid 2-letter US state code * Check that the ZIP code is in the correct format (12345 or 12345-6789) * Make sure the address is a real, serviceable US address **Expected behavior:** Valid addresses should return service options within 5-10 seconds. **Optimization tips:** * Include latitude and longitude coordinates when available * Use the summary layout for faster loading when full e-commerce isn't needed * Ensure the widget script is loaded asynchronously * Check network connectivity and CDN availability **Expected behavior:** The widget should load service options in under 10 seconds for most addresses. **Common integration issues:** * Verify the widget instance is stored globally if accessed by other scripts * Check for conflicts with other JavaScript libraries * Ensure proper error handling for failed API calls * Verify the container element is visible and properly sized **Expected behavior:** The widget should integrate seamlessly without affecting other page functionality. If issues persist, check the browser console for detailed error messages and contact support with the specific error details. ## Best Practices Always implement proper error handling for API calls: ```javascript theme={null} hum.initialize() .then(() => console.log('Success')) .catch(error => { console.error('Error:', error); // Show user-friendly error message }); ``` Use loading states to improve user experience: ```javascript theme={null} button.textContent = 'Loading...'; button.disabled = true; try { await hum.internetServiceFromAddress(addressData); } finally { button.textContent = 'Check Availability'; button.disabled = false; } ``` * Load the widget script asynchronously * Include latitude/longitude coordinates for faster results * Cache widget instances when using multiple widgets * Monitor console logs during development * Test with various address formats and edge cases * Provide clear error messages * Use appropriate layouts for your use case * Include campaign IDs for better tracking ## Next Steps Learn how to listen for and handle events from the widget Explore all available configuration parameters Integrate the widget into mobile applications Contact our team for integration assistance # Widget Overview Source: https://docs.letshum.com/widget-integration/overview Learn about the Hum widget's capabilities, features, and integration options # Hum Widget Overview The Hum widget is a turnkey solution for integrating internet service provider discovery and e-commerce functionality into your website or mobile application. With minimal setup, you can provide your users with address-specific internet plans, pricing, and the ability to order service directly. Hum widget checkout layout showing the complete e-commerce flow ## What is the Hum Widget? The Hum widget is a fully-featured, embeddable component that enables your users to: * **Search for internet service** by entering their address * **Compare plans** from 200+ internet service providers nationwide * **Filter options** by technology, speed, price, and provider * **Complete checkout** to order internet service directly * **Save plans** for later comparison and review * **View provider details** including contact information and coverage areas The widget handles all the complexity of provider data, pricing, availability checking, and checkout flows. All you need to do is embed it. ## Key Features Instant address validation and service availability from 200+ providers Choose from checkout, summary, or plans layouts to match your use case Adapts seamlessly to desktop, tablet, and mobile devices Configure colors and layout to match your brand identity Complete shopping cart and checkout flow for participating providers Listen to order completion, plan saves, and other user actions ## Architecture The Hum widget is built as a React single-page application using client-side rendering. It's designed to be: * **Performant**: Loads asynchronously without blocking page rendering * **Lightweight**: Minimal impact on your page load times * **Secure**: All API communication is encrypted and authenticated * **Reliable**: Hosted on a global CDN with 99.9% uptime The widget communicates with the Hum API backend to fetch real-time provider data, validate addresses, and process orders. Hum technology architecture diagram ## Layout Options The widget supports three layout modes to suit different use cases and user experiences: The checkout layout provides a full e-commerce experience with a shopping cart, allowing users to select plans, add-ons, and complete the ordering process. **Best for:** * Users actively seeking new internet service * Dedicated pages that support multi-step flows * Conversion-focused implementations * Real estate, mortgage, and relocation apps Checkout layout showing full shopping cart experience **Features:** * Interactive shopping cart * Multiple provider selection * Add-on services (TV, phone) * Complete checkout flow * Order confirmation The summary layout presents available internet service options in a clean, compact table format with links to provider websites. **Best for:** * Passive information discovery * Limited screen space * Quick provider comparisons * Property listing websites Summary layout showing provider options in table format **Features:** * Compact table view * Sort by speed, price, provider * Filter by technology type * Direct links to providers * Minimal screen real estate The plans layout displays provider options as cards with affiliate links, offering a middle ground between checkout and summary layouts. **Best for:** * Visual comparisons * Directing users to provider sites * Affiliate marketing use cases * Streamlined user experience Plans layout showing provider cards with affiliate links **Features:** * Card-based design * Visual plan comparisons * Provider logos and branding * Quick filtering options * Affiliate link tracking ## Integration Methods Choose the integration method that best fits your technical stack and requirements: Embed the widget using JavaScript and HTML. Ideal for websites where you control the codebase. **Best for:** Web applications, landing pages, React/Vue apps Load the widget via URL in a mobile WebView. Perfect for native and hybrid mobile apps. **Best for:** iOS, Android, React Native, Flutter apps Both integration methods provide the same features and user experience. Choose based on your technical environment. ## Configuration Options The Hum widget is highly configurable. All options are optional and have sensible defaults. ### Display & Layout Controls the visual presentation of internet service results. **Accepted Values:** * `"summary"` - Compact table format with affiliate links * `"checkout"` - Full shopping cart and e-commerce experience (default) * `"plans"` - Card layout with affiliate links **Default:** `"checkout"` Customizes the primary color for buttons and UI elements throughout the widget. **Format:** Hex color code (e.g., `"#1274f9"`) **Default:** `"#1274f9"` The color will be automatically darkened by 10% for hover states to ensure consistent user experience. Controls whether users can save and unsave internet plans for later comparison. **Default:** `false` **When enabled:** * Save/unsave button appears on plan cards * Triggers `humPlanSaved` and `humPlanUnsaved` events * Allows users to bookmark plans for future reference Enable this feature if you want to track user interest in specific plans or provide a "favorites" list functionality. Controls whether the widget renders its own address entry form above the results. **Default:** `false` **When enabled:** * An address field with autocomplete appears above the results * Selecting a suggestion starts a new session and reloads results for that address * The visitor can look up a different address without you rebuilding the widget Leave this off when your page already collects the address and passes it to `internetServiceFromAddress()`. Turn it on when you want the widget to handle address entry itself, such as on a standalone landing page. ### Tracking & Analytics Identifier for tracking attribution and analytics purposes. Associate widget sessions with specific marketing campaigns, traffic sources, or user segments. **Default:** `undefined` Include campaign identifiers like `"Q4-2024-landing-page"` or `"email-campaign-12345"` to track which sources generate the most conversions. ### Filtering & Limiting Restricts results to specific internet service providers using FCC provider IDs. **Use cases:** * Partnership agreements * White-label implementations * Regional focus Use [Get FCC Service Providers](/api-reference/informational/get-fcc-service-providers) to retrieve internet provider IDs programmatically. Designates one or more providers to display more prominently as top plan recommendations using FCC provider IDs. **Accepted Values:** Single provider ID string or array of provider ID strings **Impact:** Featured at the top of search results with visual prominence Filters results to show only specific connection technology types. **Valid options:** `Fiber`, `Cable`, `Wireless`, `Satellite`, `DSL`, `Other` Combine with `limitProviders` for precise control over displayed plans. ### Customer Data Pre-population Pre-populates customer information in the shopping cart to streamline checkout. **Available fields:** * `firstName` - Customer's first name * `lastName` - Customer's last name * `email` - Customer's email address * `phoneNumber` - Customer's phone number Pre-populating customer data can increase conversion rates by reducing friction in the checkout flow. ## Address Data Format The widget expects address data in a specific JSON format: ```json theme={null} { "street1": "1001 Woodward Ave", "street2": "Suite 500", "city": "Detroit", "state": "MI", "zip": "48226", "latitude": 42.3317, "longitude": -83.0479 } ``` ### Required Fields Primary street address containing street number and name. ZIP code in 5 or 9-digit format (12345 or 12345-6789). ### Optional Fields Secondary address information (apartment, suite, unit number). City name. Recommended for improved address match quality. Two-letter US state code (e.g., "MI" for Michigan). Recommended for improved address match quality. Latitude coordinate for improved performance and reduced latency. Longitude coordinate for improved performance and reduced latency. Both latitude and longitude must be provided together. If only one is provided, the widget will return an error. ## Common Use Cases Help homebuyers and renters understand internet options before making decisions. **Implementation tips:** * Use summary layout for listing pages * Pre-populate address from property data * Track by property ID with `campaignId` Provide comprehensive internet service information as part of relocation assistance. **Implementation tips:** * Use checkout layout for conversion * Integrate with move-in date scheduling * Pre-populate customer data from profiles Assist mortgage applicants and homebuyers in planning their move. **Implementation tips:** * Embed in post-approval flows * Use property address from loan application * Track by application ID Help tenants find and set up internet service for their new homes. **Implementation tips:** * Limit to preferred providers via partnership * Use property's address by default * Brand with property management colors Add value to listings by showing available internet services. **Implementation tips:** * Use summary layout for compact display * Cache results by address * Display alongside other amenities ## Getting Started Ready to integrate the Hum widget? Choose your integration method: Step-by-step guide for embedding the widget with JavaScript and HTML URL-based integration guide for mobile WebView applications Learn how to listen for events and handle user actions Build custom integrations using the Hum API directly ## Support Contact our team for API keys, integration assistance, and technical support # WebView Integration Source: https://docs.letshum.com/widget-integration/webview Learn how to integrate Hum into mobile applications using WebView with URL parameters # WebView Integration The Hum WebView integration provides a simplified way to embed the Hum widget experience into native mobile applications (iOS and Android) or any environment that supports WebViews. Instead of embedding JavaScript code, you simply load a URL with configuration parameters. This integration method is ideal for mobile apps built with native frameworks (Swift, Kotlin) or hybrid frameworks (React Native, Flutter) that need a quick, no-code integration path. ## Base URL All WebView integrations use the following base URL: ``` https://webview.letshum.com ``` ## Quick Start The simplest WebView integration requires only your API key: ``` https://webview.letshum.com?apiKey=YOUR_API_KEY_HERE ``` Contact your Hum representative to receive your API key if you haven't already. Build your URL by appending configuration parameters as query strings to the base URL. ``` https://webview.letshum.com?apiKey=YOUR_API_KEY&resultLayout=checkout&primaryColor=%231274f9 ``` Load the constructed URL in your application's WebView component. ```swift theme={null} import WebKit let urlString = "https://webview.letshum.com?apiKey=YOUR_API_KEY&resultLayout=checkout" if let url = URL(string: urlString) { let request = URLRequest(url: url) webView.load(request) } ``` ```kotlin theme={null} val webView: WebView = findViewById(R.id.webview) webView.settings.javaScriptEnabled = true val url = "https://webview.letshum.com?apiKey=YOUR_API_KEY&resultLayout=checkout" webView.loadUrl(url) ``` ```javascript theme={null} import { WebView } from 'react-native-webview'; function HumWebView() { const url = 'https://webview.letshum.com?apiKey=YOUR_API_KEY&resultLayout=checkout'; return ( ); } ``` ```dart theme={null} import 'package:webview_flutter/webview_flutter.dart'; class HumWebView extends StatelessWidget { final controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..loadRequest(Uri.parse( 'https://webview.letshum.com?apiKey=YOUR_API_KEY&resultLayout=checkout' )); @override Widget build(BuildContext context) { return WebViewWidget(controller: controller); } } ``` ## URL Parameters All configuration options are passed as URL query parameters. Parameters should be properly URL-encoded, especially for special characters. ### Required Parameters Your Hum API key for authentication. **Example:** `apiKey=your_api_key_here` ### Display & Layout Parameters Controls the visual presentation of internet service results. **Accepted Values:** * `summary` - Compact table format with affiliate links * `checkout` - Full e-commerce shopping cart experience (default) * `plans` - Card layout with affiliate links **Default:** `checkout` **Example:** `resultLayout=summary` Customizes the primary color for buttons and UI elements. Must be a URL-encoded hex color code. **Format:** Hex color without the `#` symbol, or URL-encoded with `%23` **Default:** `1274f9` (Hum blue) **Examples:** * `primaryColor=1274f9` * `primaryColor=%23FF5733` Controls whether users can save and unsave internet plans for later comparison. **Accepted Values:** `true` or `false` **Default:** `false` **Example:** `showSavePlanButton=true` **When enabled:** * Save/unsave button appears on plan cards * Triggers `humPlanSaved` and `humPlanUnsaved` events * Allows users to bookmark plans for future reference Controls whether the widget renders its own address entry form above the results. **Accepted Values:** `true` or `false` **Default:** `false` **Example:** `showAddressCompletionForm=true` **When enabled:** * An address field with autocomplete appears above the results * Selecting a suggestion starts a new session and reloads results for that address * The visitor can look up a different address without the host app reloading the WebView Leave this off when your app already passes the address through the URL parameters below. Turn it on when you want the visitor to be able to enter or change the address inside the WebView. ### Address Parameters Pre-populate the address to show results immediately when the WebView loads. Primary street address (street number and name). **Example:** `street1=123%20Main%20St` When providing an address, `street1` and `zip` are required. `city` and `state` are optional but recommended for improved address match quality. Secondary address information (apartment, suite, unit number). **Example:** `street2=Apt%20205` City name. **Example:** `city=Detroit` Two-letter state code. **Example:** `state=MI` ZIP code (5 or 9 digits). **Example:** `zip=48226` Alternative to individual address fields - provide the complete address as a single string. **Example:** `s=123%20Main%20St%20Apt%204B%2C%20Detroit%2C%20MI%2048226` Unit or apartment designators included in the single address string are parsed into `street2` automatically. When using the `s` parameter, do not include `street1`, `city`, `state`, or `zip` parameters. Use either `s` OR the individual fields, not both. Latitude coordinate for the address (improves performance). **Example:** `latitude=42.3317` Both `latitude` and `longitude` must be provided together. Longitude coordinate for the address (improves performance). **Example:** `longitude=-83.0479` ### Tracking & Analytics Parameters Identifier for tracking attribution and analytics. Use this to associate sessions with marketing campaigns, traffic sources, or user segments. **Example:** `campaignId=mobile-app-Q4-2024` ### Customer Data Parameters Pre-populate customer information to streamline the checkout process. Customer's first name. **Example:** `firstName=Jane` Customer's last name. **Example:** `lastName=Smith` Customer's email address (must be URL-encoded). **Example:** `email=jane.smith%40example.com` Customer's phone number. **Example:** `phoneNumber=555-123-4567` ### Filtering Parameters Comma-separated list of FCC provider IDs to restrict results to specific providers. **Example:** `limitProviders=130077,130317` Use [Get FCC Service Providers](/api-reference/informational/get-fcc-service-providers) to retrieve provider IDs programmatically. Comma-separated list of FCC provider IDs to feature prominently at the top of results. **Example:** `primaryProviders=130077,130317` Comma-separated list of technology types to filter results. **Accepted Values:** `Fiber`, `Cable`, `Wireless`, `Satellite`, `DSL`, `Other` **Example:** `limitTechnologies=Fiber,Cable` ## Complete URL Examples ### Example 1: Basic Integration Minimal configuration with just the API key: ``` https://webview.letshum.com?apiKey=your_api_key_here ``` ### Example 2: With Pre-populated Address Show results for a specific address immediately: ``` https://webview.letshum.com?apiKey=your_api_key_here&street1=123%20Main%20St&city=Detroit&state=MI&zip=48226 ``` ### Example 3: Full Address String Using the single address parameter: ``` https://webview.letshum.com?apiKey=your_api_key_here&s=123%20Main%20St%2C%20Detroit%2C%20MI%2048226 ``` ### Example 4: Customized Layout and Branding Change the layout and primary color: ``` https://webview.letshum.com?apiKey=your_api_key_here&resultLayout=summary&primaryColor=%23FF5733 ``` ### Example 5: Pre-populated Customer Data Streamline checkout with known customer information: ``` https://webview.letshum.com?apiKey=your_api_key_here&street1=123%20Main%20St&city=Detroit&state=MI&zip=48226&firstName=Jane&lastName=Smith&email=jane.smith%40example.com&phoneNumber=555-123-4567 ``` ### Example 6: Filtered Providers and Technologies Show only fiber and cable from specific providers: ``` https://webview.letshum.com?apiKey=your_api_key_here&street1=123%20Main%20St&city=Detroit&state=MI&zip=48226&limitProviders=130077,130317&limitTechnologies=Fiber,Cable ``` ### Example 7: Complete Configuration All parameters combined: ``` https://webview.letshum.com?apiKey=your_api_key_here&resultLayout=checkout&primaryColor=%231274f9&street1=123%20Main%20St&city=Detroit&state=MI&zip=48226&latitude=42.3317&longitude=-83.0479&campaignId=mobile-app-Q4-2024&firstName=Jane&lastName=Smith&email=jane.smith%40example.com&phoneNumber=555-123-4567&limitProviders=130077,130317&primaryProviders=130077&limitTechnologies=Fiber,Cable ``` ## URL Encoding Reference When constructing URLs, ensure special characters are properly encoded: | Character | Encoded Value | Example | | --------- | ------------- | ----------------------------------------- | | Space | `%20` | `Main St` → `Main%20St` | | `#` | `%23` | `#FF5733` → `%23FF5733` | | `@` | `%40` | `user@example.com` → `user%40example.com` | | `&` | `%26` | `AT&T` → `AT%26T` | | `,` | `%2C` | `MI, 48226` → `MI%2C%2048226` | Most programming languages provide built-in URL encoding functions. Use these instead of manually encoding characters to avoid errors. ## Platform-Specific Implementation ### iOS Implementation ```swift theme={null} import WebKit class HumWebViewController: UIViewController { var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() // Configure WebView let webConfiguration = WKWebViewConfiguration() webView = WKWebView(frame: view.bounds, configuration: webConfiguration) webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(webView) } } ``` ```swift theme={null} func loadHumWebView() { var components = URLComponents(string: "https://webview.letshum.com")! components.queryItems = [ URLQueryItem(name: "apiKey", value: "your_api_key_here"), URLQueryItem(name: "resultLayout", value: "checkout"), URLQueryItem(name: "street1", value: "123 Main St"), URLQueryItem(name: "city", value: "Detroit"), URLQueryItem(name: "state", value: "MI"), URLQueryItem(name: "zip", value: "48226") ] if let url = components.url { let request = URLRequest(url: url) webView.load(request) } } ``` `URLComponents` automatically handles URL encoding for you. ### Android Implementation ```xml res/layout/activity_main.xml theme={null} ``` ```kotlin theme={null} import android.webkit.WebView import android.webkit.WebSettings import java.net.URLEncoder class MainActivity : AppCompatActivity() { private lateinit var webView: WebView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) webView = findViewById(R.id.hum_webview) setupWebView() loadHumWebView() } private fun setupWebView() { webView.settings.apply { javaScriptEnabled = true domStorageEnabled = true loadWithOverviewMode = true useWideViewPort = true } } private fun loadHumWebView() { val params = mapOf( "apiKey" to "your_api_key_here", "resultLayout" to "checkout", "street1" to "123 Main St", "city" to "Detroit", "state" to "MI", "zip" to "48226" ) val queryString = params.entries.joinToString("&") { (key, value) -> "$key=${URLEncoder.encode(value, "UTF-8")}" } val url = "https://webview.letshum.com?$queryString" webView.loadUrl(url) } } ``` ### React Native Implementation ```javascript theme={null} import React from 'react'; import { WebView } from 'react-native-webview'; import { SafeAreaView, StyleSheet } from 'react-native'; function HumWebViewScreen() { const buildUrl = () => { const baseUrl = 'https://webview.letshum.com'; const params = { apiKey: 'your_api_key_here', resultLayout: 'checkout', street1: '123 Main St', city: 'Detroit', state: 'MI', zip: '48226', firstName: 'Jane', lastName: 'Smith', email: 'jane.smith@example.com' }; const queryString = Object.entries(params) .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) .join('&'); return `${baseUrl}?${queryString}`; }; return ( ); } const styles = StyleSheet.create({ container: { flex: 1, }, }); export default HumWebViewScreen; ``` ## Listening to Widget Events The WebView integration supports the same hook notifications as the standard widget integration. See the [Widget Hook Notifications](/widget-integration/hooks) page for detailed information on handling order completion, plan save, and plan unsave events. Configure your WebView to handle postMessage events to receive notifications from the Hum widget. ## Best Practices The Hum widget requires JavaScript and DOM storage to function properly. Ensure these are enabled in your WebView configuration. ```swift theme={null} // iOS webView.configuration.preferences.javaScriptEnabled = true ``` ```kotlin theme={null} // Android webView.settings.javaScriptEnabled = true webView.settings.domStorageEnabled = true ``` Consider restricting navigation to keep users within the Hum experience or handle external links appropriately. ```swift theme={null} // iOS func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if let url = navigationAction.request.url?.absoluteString { if url.contains("webview.letshum.com") { decisionHandler(.allow) } else { // Open external links in Safari UIApplication.shared.open(navigationAction.request.url!) decisionHandler(.cancel) } } } ``` Handle WebView errors gracefully to improve user experience. ```kotlin theme={null} // Android webView.webViewClient = object : WebViewClient() { override fun onReceivedError( view: WebView?, request: WebResourceRequest?, error: WebResourceError? ) { // Show error message to user Toast.makeText( this@MainActivity, "Failed to load content. Please check your connection.", Toast.LENGTH_LONG ).show() } } ``` If your app already has user information or location data, pass it as URL parameters to reduce friction and improve conversion rates. ```javascript theme={null} // React Native example const getUserData = () => ({ firstName: user.firstName, lastName: user.lastName, email: user.email, phoneNumber: user.phone }); const getLocationData = () => ({ street1: location.street, city: location.city, state: location.state, zip: location.zip }); ``` ## Troubleshooting **Possible causes:** * JavaScript is disabled in WebView settings * DOM storage is disabled * Network connectivity issues * Invalid API key **Solutions:** * Verify JavaScript is enabled * Enable DOM storage * Check network connection * Validate your API key with Hum support **Possible causes:** * Missing required address fields * Incorrect URL encoding * Using both `s` parameter and individual fields **Solutions:** * Ensure the required fields (`street1` and `zip`) are present * Include `city` and `state` when available to improve address match quality * Use proper URL encoding functions * Use either `s` OR individual fields, not both **Possible causes:** * Hex color not URL-encoded * Invalid hex color format **Solutions:** * Use `%23` for the `#` symbol: `primaryColor=%23FF5733` * Or omit the `#` entirely: `primaryColor=FF5733` * Verify the hex color is valid ## Next Steps Learn how to listen for and handle events from the Hum widget Explore the full JavaScript widget integration for web applications Build custom integrations using the Hum API Contact our team for integration assistance