Last updated

Hubby WebView

Hubby provides a fully managed web app that runs inside your mobile app's in-app browser. Your travelers claim packages, install their eSIM, monitor data usage, and buy top-ups — all within a branded experience you control. Your backend makes two API calls. Hubby handles everything else.

This page covers the complete flow: what you build, what Hubby handles, every API call in sequence, how authentication works, and what to do after you go live.


What You Build vs. What Hubby Handles

Your teamHubby
Backend calls to create bookings and redirect tokensPackage provisioning, eSIM assignment, region optimization
WebView container in your app (open a URL)Branded UI: claiming flow, installation instructions, data meter, top-up store
Pass device info as query parameters when opening the WebViewDevice-tailored installation guides, localized UI
Store external_user_id per travelerSession management, token exchange, JWT lifecycle
Map webhook events to push notifications (Phase 2)Webhook event delivery, departure-time CRM triggers

Effort estimate: Backend: 3–5 days. Mobile: 2–3 days. No frontend build required — your app opens a URL.


Architecture

Three actors are involved: your backend, the Hubby API, and the Hubby WebView running inside your app.

Hubby WebView(in Partner App)Hubby APIPartner BackendHubby WebView(in Partner App)Hubby APIPartner BackendPOST /api/bookingsbooking confirmation + package IDsPOST /api/redirect-tokens/createredirect_token (valid 5 min)Open WebView with redirect_tokenPOST /api/webapp/auth/exchangesession JWTGET /api/webapp/me/dashboarduser dashboard dataUser claims packages,installs eSIM, monitors data

The Full Flow

Step 1 — Create a Booking

POST /api/bookings — your backend calls this when a traveler qualifies for a package (e.g., at flight booking time, loyalty reward, or campaign trigger). The Booking entity maps directly to the travel booking you already have — a departure date, one or more destinations, and the bundles (data packages) for each.

Request Fields

FieldTypeRequiredDescription
departure_datestringYesISO 8601 datetime with timezone (e.g., 2026-07-15T14:30:00+02:00). Including the time and timezone enables precise CRM triggers such as push notifications 2 hours before departure
localestringNoWebView language (es, fr, de, etc.). See supported locales
custom_brandingstringNoSelects your branded theme. Partners with multiple sub-brands pass the brand key here
package_specificationsarrayYesOne or more packages to provision

Package Specification Fields

FieldTypeRequiredDescription
external_user_idstringYesRequired for WebView integration. Your own identifier for the traveler receiving this package. This is the key that links the booking to the traveler across all subsequent calls — redirect tokens, WebView auth exchange, and sessions. Use your own internal user ID and pass the same value consistently.
destinationstringYesISO 3166-1 alpha-2 country code (GR, US, JP)
sizestringYesData package size (1GB, 3GB, 5GB)

Important: external_user_id is required for the WebView flow. It must be your own stable user identifier, and the same value must be used across all WebView endpoints — bookings, redirect tokens, and the auth exchange. If you are not using the WebView or Native eSIM Integration, do not supply this field — it is ignored in the traditional booking flow and supplying it incorrectly will route the package into the eSIM management flow.

Multi-Destination Bookings

A single booking can contain multiple entries in package_specifications. Hubby automatically optimizes the assignment — for example, a trip covering Greenland, Canada, the US, and Mexico may be fulfilled with a single North America regional package plus individual packages where more cost-effective.

Example

curl --request POST \
  'https://api.hubbyesim.com/api/bookings' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'x-timestamp: 1717689600000' \
  --header 'x-signature: YOUR_HMAC_SIGNATURE' \
  --header 'Content-Type: application/json' \
  --data '{
    "departure_date": "2026-07-15T14:30:00+02:00",
    "locale": "es",
    "custom_branding": "your-brand",
    "package_specifications": [
      {
        "external_user_id": "partner_user_456",
        "destination": "GR",
        "size": "1GB"
      }
    ]
  }'
{
  "success": true,
  "data": {
    "id": "booking_abc",
    "package_specifications": [
      {
        "external_user_id": "partner_user_456",
        "package_id": "pkg_xyz",
        "destination": "GR",
        "size": "1GB",
        "status": "provisioned"
      }
    ]
  }
}

Note: external_user_id is required for the WebView flow. Always supply your own consistent user identifier — it is the key that ties the booking, redirect token, and WebView session together.


Step 2 — Create a Redirect Token

POST /api/redirect-tokens/create — your backend calls this right before the traveler opens the eSIM section in your app. The response contains a short-lived redirect_token that your app uses to open the Hubby WebView.

Request Fields

FieldTypeRequiredDescription
external_user_idstringYesYour own user identifier — the same value you used in the booking's package_specifications
redirect_tokenstringNoIf you pre-generate the token, pass it here. Otherwise Hubby generates one

Token Lifetime

The redirect token is valid for 5 minutes. This prevents URL forwarding and sharing. If the token expires before the traveler opens the WebView, request a fresh one from the same endpoint.

Important: Do NOT cache the redirect_token. Generate a new token every time the traveler opens the eSIM section in your app — on every open, on app resume if the WebView was killed by the OS, and if the 5-minute window expires.

Example

curl --request POST \
  'https://api.hubbyesim.com/api/redirect-tokens/create' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'x-timestamp: 1717689600000' \
  --header 'x-signature: YOUR_HMAC_SIGNATURE' \
  --header 'Content-Type: application/json' \
  --data '{
    "external_user_id": "partner_user_456"
  }'
{
  "success": true,
  "data": {
    "redirect_token": "1b80dfe9-0202-4151-a26f-ceac52ca3b34",
    "expires_in": 300
  }
}

Step 3 — Open the WebView

Your app opens the Hubby WebView in an in-app browser using the redirect_token returned in Step 2. Construct the URL as https://app.hubbyesim.com?t={redirect_token} and append device information as additional query parameters so the WebView can tailor eSIM installation instructions to the traveler's exact device.

Device Query Parameters

ParameterTypeDescription
phone_osstringOperating system (iOS or Android)
phone_modelstringDevice model (iPhone 15 Pro, Samsung Galaxy S24)
os_versionstringOS version (17.4, 14)
phone_brandstringDevice manufacturer (Apple, Samsung, Google)

All parameters are optional. The WebView also performs its own browser-based device detection, but passing these values from the native app provides more accurate results — especially for device model and brand, which browsers cannot reliably detect.

Example

https://app.hubbyesim.com?t=1b80dfe9-0202-4151-a26f-ceac52ca3b34&phone_os=iOS&phone_model=iPhone%2015%20Pro&os_version=17.4&phone_brand=Apple

Tip: URL-encode parameter values. These parameters are read client-side by the WebView — they are never sent to the Hubby backend.

What the Traveler Sees

The WebView adapts to the traveler's state:

Traveler has unclaimed packages → A congratulations screen appears ("You got 1GB for Greece!"). The traveler can:

  • Accept — starts the eSIM installation flow, tailored to their exact device and OS version
  • Skip — moves to the next unclaimed package

After all packages are claimed (or skipped), the traveler sees the data meter.

No unclaimed packages → The data meter screen shows:

  • Active package status and remaining data
  • Option to buy more data
  • If all packages have expired, a "Get a package" prompt

Session Behaviour

  • Closing the WebView ends the session. The next time the traveler opens it, your app should generate a new redirect token (Step 2) and re-open.
  • Auth token persists across navigations within the same WebView session — no re-authentication while the browser instance is alive.
  • If the OS kills the WebView in the background, generate a new redirect token on app resume.

eSIM Installation URL Handling

When the traveler taps "Install eSIM" inside the WebView, the web app navigates to a system-level provisioning URL. These look like normal https:// links but are handled by the OS, not by a browser. A WebView will silently fail to load them unless your native app intercepts and hands them off to the OS.

URLs the web app navigates to:

PlatformURL
Androidhttps://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$<smdp-address>$<matching-id>
iOShttps://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$<smdp-address>$<matching-id>

The carddata query parameter contains the GSMA LPA activation code in the format LPA:1$<SM-DP+ address>$<matching ID>.

Why this is needed: These URLs are not real websites. On Android, esimsetup.android.com is an Android App Link registered by the system eSIM provisioning service — it only works via startActivity with ACTION_VIEW. On iOS, esimsetup.apple.com is handled by the iOS Cellular Setup service — it only works via UIApplication.open(_:). If your WebView does not intercept these URLs, the navigation fails silently and the user sees nothing happen.

Your app must intercept these URL patterns and launch them externally:

URL PatternPlatformAction
https://esimsetup.android.com/*AndroidLaunch with OS via Intent / ACTION_VIEW
https://esimsetup.apple.com/*iOSLaunch with OS via UIApplication.open
LPA:1$...BothLaunch externally (GSMA standard scheme)

Override shouldOverrideUrlLoading in your WebViewClient:

webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(
        view: WebView,
        request: WebResourceRequest
    ): Boolean {
        val url = request.url.toString()

        // eSIM provisioning URL — launch with the OS
        if (url.startsWith("https://esimsetup.android.com/")) {
            val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
            startActivity(intent)
            return true
        }

        // Direct LPA activation code
        if (url.startsWith("LPA:")) {
            val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
            startActivity(intent)
            return true
        }

        return false
    }
}

Testing: After implementing, create a booking and open the WebView. Tap "Install eSIM" — the device's native eSIM provisioning dialog should appear. If nothing happens, check your debug logs for the intercepted URL.


Step 4 — Token Exchange (automatic)

Partners do not call this endpoint. It is documented here for transparency and to help debug WebView network traffic.

When the WebView loads with the redirect_token, it automatically calls:

POST /api/webapp/auth/exchange

{
  "redirect_token": "1b80dfe9-0202-4151-a26f-ceac52ca3b34",
  "external_user_id": "partner_user_456"
}

This exchanges the short-lived redirect token for a session JWT. The JWT is scoped to /webapp/ endpoints only — it cannot be used for partner backend calls and your API keys are never exposed to the WebView.


Step 5 — Dashboard Load (automatic)

Partners do not call this endpoint. Documented for transparency.

The WebView uses the session JWT to load the traveler's dashboard:

GET /api/webapp/me/dashboard

curl --request GET \
  'https://api.hubbyesim.com/api/webapp/me/dashboard' \
  --header 'Authorization: Bearer <session_jwt>' \
  --header 'Content-Type: application/json'

The response includes active packages, data usage, unclaimed packages, and available actions. The WebView renders this into the branded UI.


How Authentication Works

The WebView flow uses two separate auth contexts. They never mix.

Your Backend → Hubby API (HMAC-SHA256)

All calls from your backend use HMAC-SHA256 signing. Every request includes:

  • x-api-key — your public API key
  • x-timestamp — current Unix timestamp in milliseconds
  • x-signature — HMAC-SHA256 of {timestamp}{METHOD}{path}

This covers POST /api/bookings, POST /api/redirect-tokens/create, and all other partner-facing endpoints. See the authentication guide for implementation details.

WebView → Hubby API (JWT)

The WebView authenticates with a short-lived JWT obtained through the token exchange (Step 4). This is completely separate from your HMAC credentials — the WebView never has access to your API keys.

Hubby WebViewHubby APIPartner BackendHubby WebViewHubby APIPartner BackendHMAC-SHA256JWTPOST /api/redirect-tokens/createredirect_tokenPOST /api/webapp/auth/exchangesession JWTGET /api/webapp/me/dashboarddashboard data

Security Properties

  • Time-limited tokens — the redirect_token expires in 5 minutes, preventing forwarding/sharing
  • No PII in the URL — the WebView URL contains only the opaque redirect_token and optional device metadata
  • Client-side device info — device parameters are read by the WebView client-side and never sent to the Hubby backend
  • Scoped JWT — valid only for /webapp/ endpoints, tied to the external_user_id

Identifier Reference

A single identifier — external_user_id — ties everything together across the WebView flow.

EndpointHow external_user_id is used
POST /api/bookingspackage_specifications[]Links each package to a specific traveler
POST /api/redirect-tokens/createTies the WebView session to the traveler
POST /api/webapp/auth/exchangeValidates the traveler matches the redirect token
POST /api/webapp/refresh-esimIdentifies which user to assign the fresh eSIM to

Use your own internal user ID as the external_user_id and pass the same value consistently across all three endpoints. This field is required for the WebView flow — the redirect token and auth exchange will fail without it.


Refresh Universal eSIM

POST /api/webapp/refresh-esim — assigns a fresh universal eSIM to a user. If the user already has a universal eSIM, it is replaced. If they don't have one yet, a new eSIM is provisioned and assigned.

Use this to pre-provision an eSIM before the traveler opens the WebView, or to replace a problematic eSIM with a fresh one.

Authentication: Bearer JWT from /webapp/auth/exchange.

Request Fields

FieldTypeRequiredDescription
external_user_idstringYesYour own user identifier — the same value used in bookings and redirect tokens

Example

curl --request POST \
  'https://api.hubbyesim.com/api/webapp/refresh-esim' \
  --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIs...' \
  --header 'Content-Type: application/json' \
  --data '{
    "external_user_id": "partner_user_456"
  }'
{
  "success": true,
  "data": {
    "iccid": "8901234567890123456",
    "qr": "LPA:1$smdp.example.com$ACTIVATION-CODE",
    "status": "RELEASED"
  }
}

Note: This endpoint is idempotent in intent — calling it always results in a fresh eSIM assigned to the user, whether or not they had one before. The previous eSIM (if any) is replaced.


What Comes Next

The WebView gets you live fast. Once it's running, you can progressively enhance the experience:

Communication & Conversion (Weeks 2–4)

Use webhooks to receive real-time events and map them to push notifications:

  • booking.within_cutoff — departure is 7 days away, send an installation reminder
  • booking.about_to_depart — 2 hours before flight (requires time + timezone in departure_date), last-chance install prompt
  • esim.installed / esim.removed — track installation state
  • package.activated — traveler connected at destination
  • package.usage.20_percent / package.usage.50_percent / package.usage.80_percent — per-package usage milestones (data consumed or duration elapsed), drive top-up revenue at 80%

Branding & Segmentation

Use custom_branding to run separate branded themes per sub-brand. Different customer segments can receive different package sizes (e.g., premium members get 3GB, standard members get 1GB) by varying size in package_specifications.

Zero-Rated Traffic

Ensure your core app functionality (boarding passes, booking management, payments) remains accessible to travelers even when their data package is depleted. Provide your critical IP ranges to Hubby and we configure zero-rated traffic policies.

Native API (Optional)

For partners who want to go beyond the WebView, Hubby offers a Native API covering packages, provisioning, installation instructions, usage reporting, and event streams. The WebView and Native API coexist — many partners start with the WebView and selectively move high-traffic features (data meter, top-up) to native over time.


Troubleshooting

"Token expired" error in WebView

The redirect token was not opened within 5 minutes. Generate a new token and re-open.

"Invalid external_user_id" error

The external_user_id in the token exchange does not match the one used when creating the redirect token. Ensure your app passes the same identifier consistently.

Nothing happens when traveler taps "Install eSIM"

Your WebView is loading the provisioning URL (esimsetup.android.com or esimsetup.apple.com) as a webpage instead of launching it externally. Implement the URL interception described in eSIM Installation URL Handling.

WebView shows a blank screen

Check your network inspector for a failed /webapp/auth/exchange call. Common causes:

  • Redirect token already consumed (tokens are single-use)
  • Network timeout during the exchange
  • WebView security settings blocking the request (ensure JavaScript is enabled and cross-origin requests are allowed for app.hubbyesim.com)

Quick-Start Checklist

To go live, your team provides:

  • User identifier format (your internal user ID that becomes external_user_id)
  • Brand assets per sub-brand (logos, colors, copy)
  • Required locales for the WebView
  • Customer segment → package size mapping
  • Technical point of contact

Hubby provides:

  • API keys (staging + production), per sub-brand if needed
  • Branded WebView configured per sub-brand
  • Staging environment with all endpoints
  • Integration support