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 team | Hubby |
|---|---|
| Backend calls to create bookings and redirect tokens | Package 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 WebView | Device-tailored installation guides, localized UI |
Store external_user_id per traveler | Session 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.
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
| Field | Type | Required | Description |
|---|---|---|---|
departure_date | string | Yes | ISO 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 |
locale | string | No | WebView language (es, fr, de, etc.). See supported locales |
custom_branding | string | No | Selects your branded theme. Partners with multiple sub-brands pass the brand key here |
package_specifications | array | Yes | One or more packages to provision |
Package Specification Fields
| Field | Type | Required | Description |
|---|---|---|---|
external_user_id | string | Yes | Required 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. |
destination | string | Yes | ISO 3166-1 alpha-2 country code (GR, US, JP) |
size | string | Yes | Data package size (1GB, 3GB, 5GB) |
Important:
external_user_idis 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_idis 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
| Field | Type | Required | Description |
|---|---|---|---|
external_user_id | string | Yes | Your own user identifier — the same value you used in the booking's package_specifications |
redirect_token | string | No | If 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
| Parameter | Type | Description |
|---|---|---|
phone_os | string | Operating system (iOS or Android) |
phone_model | string | Device model (iPhone 15 Pro, Samsung Galaxy S24) |
os_version | string | OS version (17.4, 14) |
phone_brand | string | Device 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=AppleTip: 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:
| Platform | URL |
|---|---|
| Android | https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$<smdp-address>$<matching-id> |
| iOS | https://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 Pattern | Platform | Action |
|---|---|---|
https://esimsetup.android.com/* | Android | Launch with OS via Intent / ACTION_VIEW |
https://esimsetup.apple.com/* | iOS | Launch with OS via UIApplication.open |
LPA:1$... | Both | Launch 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 keyx-timestamp— current Unix timestamp in millisecondsx-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.
Security Properties
- Time-limited tokens — the
redirect_tokenexpires in 5 minutes, preventing forwarding/sharing - No PII in the URL — the WebView URL contains only the opaque
redirect_tokenand 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 theexternal_user_id
Identifier Reference
A single identifier — external_user_id — ties everything together across the WebView flow.
| Endpoint | How external_user_id is used |
|---|---|
POST /api/bookings → package_specifications[] | Links each package to a specific traveler |
POST /api/redirect-tokens/create | Ties the WebView session to the traveler |
POST /api/webapp/auth/exchange | Validates the traveler matches the redirect token |
POST /api/webapp/refresh-esim | Identifies 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
| Field | Type | Required | Description |
|---|---|---|---|
external_user_id | string | Yes | Your 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 reminderbooking.about_to_depart— 2 hours before flight (requires time + timezone indeparture_date), last-chance install promptesim.installed/esim.removed— track installation statepackage.activated— traveler connected at destinationpackage.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
Related
- API Reference — WebView endpoints — full OpenAPI spec for redirect tokens, token exchange, and dashboard
- Authentication guide — HMAC-SHA256 setup
- Multi-destination bookings — advanced booking patterns
- Branding — configure branded themes
- Webhooks — real-time event delivery
- Supported locales — available WebView languages