Hubby API (2.1.0)

Welcome to the Hubby API documentation. This API enables partners to seamlessly integrate eSIM booking and management capabilities into their applications.

Key Features:

  • Create and manage eSIM bookings for your customers
  • Access our global package catalog with country-specific offerings
  • Track booking statuses and package activations
  • Support for multiple package types (starter, data-limited, unlimited)

Package Types

The API supports four types of packages:

  • Starter packages: Hybrid packages that are both data and time limited. They provide a small data allowance within a short time period (default: 2 days). Perfect for first-time users and trials.

  • Data-limited packages: Traditional packages with a specific data allowance that expires after a certain period (default: 365 days). This is the primary package type and the default for most use cases.

  • Unlimited packages: Packages that provide unrestricted data usage for a specified duration. Subject to fair use policy. Ideal for heavy data users and digital nomads.

  • Time-limited packages (deprecated): Packages that provide a fixed data allowance for a specific duration with full-speed access. They expire when either the data limit or time limit is reached. This package type is deprecated and should not be used for new integrations — use a data-limited package with a short package_duration instead.

Note: Top-ups are always data-limited packages, regardless of the original package type.

Authentication: All API requests must include the following headers:

  • x-api-key: Your public API key
  • x-timestamp: Current Unix timestamp in milliseconds
  • x-signature: HMAC-SHA256 signature

The HMAC signature must be generated for each request using:

  1. Concatenate: timestamp + HTTP method + request path Example: "1678901234GET/bookings"
  2. Generate HMAC-SHA256 using your secret key
  3. Convert to hex string

Note: Swagger UI cannot be used to test the API directly as each request requires a unique HMAC signature. Please implement the authentication in your client application.

Example Node.js Implementation:

const cryptoJs = require('crypto-js');

// Configuration values that would normally come from environment
const secretKey = "YOUR_API_SECRET";
const publicKey = "YOUR_API_KEY";
const baseUrl = "YOUR_BASE_URL";

// Function to generate headers for API request
function generateApiHeaders(method, path) {
    //Timestamp is in milliseconds e.g. 1715558400000
    const timestamp = Math.floor(Date.now()).toString();

    // Ensure url is a string
    let path = String(url);

    // Remove baseUrl from the url if present
    path = processedUrl.replace(baseUrl, '');

    // Create query string if needed
    const queryString = new URL(url).search;
    if (queryString) {
        processedUrl += queryString;
    }

    // Validate public key
    if (!publicKey) {
        throw new Error("Public key is required");
    }

    // Create the payload
    // Sample payload: 1715558400000GET/bookings?bookingId=1234567890
    const payload = timestamp + method + path;

    // Generate the HMAC signature
    const signature = cryptoJs.HmacSHA256(payload, secretKey).toString(cryptoJs.enc.Hex);

    // Return headers object
    return {
        'x-timestamp': timestamp,
        'x-signature': signature,
        'x-api-key': publicKey,
        'Accept': 'application/json'
    };
}

Webhook Delivery Authentication

When Hubby delivers a webhook to your endpoint, the request carries your API key in a header. By default this is x-api-key: <your-key>. If your endpoint expects the key under a different header, the header name is configurable per partner (for example Authorization) with an optional value prefix (for example Bearer , producing Authorization: Bearer <your-key>). The default remains x-api-key with no prefix — contact support@hubbyesim.com to change it.

Signature verification

When a signing secret is configured, every webhook is signed so you can verify it originated from Hubby and was not altered in transit. Each delivery carries these headers:

  • x-hubby-signature — one or more comma-separated sha256=<hex> values. Verify by recomputing HMAC-SHA256(secret, "{timestamp}.{body}"), hex-encoding the result, and accepting the request if it matches any entry in the list. Signatures are lowercase hex. Treat this as a list from day one: today we send a single signature, but the list form lets us add a second signature during a future secret rotation without any change on your side.
  • x-hubby-timestamp — the signing timestamp, in epoch seconds. This is the {timestamp} in the signed string above.

The signed string is the timestamp, a literal ., then the exact raw request body as received. Verify against the raw bytes — do not re-serialize the JSON first, as key reordering or whitespace changes will break the match.

Reject the request if now - x-hubby-timestamp exceeds your tolerance window (we recommend ~5 minutes). This is your protection against replayed requests.

A secret stays valid until it is regenerated; there is no scheduled expiry. Regeneration is a hard cutover — update your stored secret at the same time. You receive separate secrets for staging and production.

Idempotency & event identity

Two identifiers accompany every delivery, both as headers and inside the JSON body (in case reading custom headers is awkward in your stack):

  • x-hubby-event-id / event_id — a stable identifier for the logical event. It stays identical across retries and across any replay of the same event, so deduplicate on it: processing the same event_id more than once should be a no-op on your side.
  • x-hubby-delivery-id / delivery_id — identifies a single delivery. Quote it when contacting support about a specific delivery. A replay of an event carries the same event_id but a new delivery_id.

Delivery, retries & replay

  • A delivery is considered successful when your endpoint returns any 2xx status.
  • If your endpoint returns 5xx or 429, times out, or is unreachable, Hubby retries with exponential backoff over a few hours (up to 12 attempts total).
  • A 4xx (other than 429) is treated as a permanent rejection of the content and is not retried. Return 2xx for "received" and reserve 4xx for genuinely malformed requests.
  • Every delivery is recorded and any past event can be replayed to you on request. A replay carries the same event_id (so your dedup keeps it safe) with a fresh signature and timestamp.
  • Deliveries are independent and ordering is not guaranteed; rely on event_id for deduplication rather than on arrival order.

Need Help?

  • Technical Support: support@hubbyesim.com
Download OpenAPI description
Languages
Servers
Mock server
https://docs.hubbyesim.com/_mock/apis/openapi/
Production server
https://api.hubbyesim.com/api/
Staging server
https://api-staging.hubby.dev/api/

Booking

Operations

Destination

Operations

PromoCode

Operations

eSIM

Operations

WebView

Operations

Create a redirect token

Request

Generates a short-lived redirect token that the partner app uses to open the Hubby WebView. The token is valid for 5 minutes and is single-use.

When to call: Every time the traveler opens the eSIM section in your app. Do not cache the returned token — generate a fresh token on every open, on app resume, and if the 5-minute window expires.

Authentication: HMAC-SHA256 (same as all partner endpoints).

Bodyapplication/jsonrequired
Any of:

Identify the traveler with either external_user_id or email. When both are supplied, external_user_id takes precedence.

external_user_idstringrequired

The partner's own user identifier — your internal ID for this traveler. This is not Hubby's user_id from the booking API. Use this for travelers created via the WebView / universal flow.

Example: "partner_user_456"
emailstring(email)

Alternative to external_user_id for partners without a stable per-user identifier (e.g. classic-grant / email-keyed bookings). Resolves the traveler by email, scoped to your Partner. If the resolved user has no external_user_id yet, Hubby assigns one so subsequent calls remain stable.

Example: "traveller@example.com"
redirect_tokenstring(uuid)

Optional. If you pre-generate the token, pass it here. Otherwise Hubby generates one.

Example: "b3864ab6-c13b-42b5-9f69-935eb9c34aba"
curl -i -X POST \
  https://docs.hubbyesim.com/_mock/apis/openapi/redirect-tokens/create \
  -H 'Content-Type: application/json' \
  -d '{
    "external_user_id": "partner_user_456",
    "email": "traveller@example.com",
    "redirect_token": "b3864ab6-c13b-42b5-9f69-935eb9c34aba"
  }'

Responses

Redirect token created successfully

Bodyapplication/json
successboolean
Example: true
dataobject
Response
application/json
{ "success": true, "data": { "redirect_token": "1b80dfe9-0202-4151-a26f-ceac52ca3b34", "expires_in": 300 } }

Exchange redirect token for session JWT

Request

Exchanges a redirect token for a short-lived session JWT. The JWT is used as a Bearer token for all subsequent WebView API calls.

Called automatically by the Hubby WebView — partners do not call this endpoint directly. Documented for transparency and to aid debugging of WebView network traffic.

Authentication: None (the redirect token itself is the credential).

Bodyapplication/jsonrequired
redirect_tokenstring(uuid)required

The redirect token returned by /redirect-tokens/create.

Example: "1b80dfe9-0202-4151-a26f-ceac52ca3b34"
external_user_idstringrequired

The partner's own user identifier. Must match the external_user_id used when creating the redirect token.

Example: "123"
curl -i -X POST \
  https://docs.hubbyesim.com/_mock/apis/openapi/webapp/auth/exchange \
  -H 'Content-Type: application/json' \
  -d '{
    "redirect_token": "1b80dfe9-0202-4151-a26f-ceac52ca3b34",
    "external_user_id": "123"
  }'

Responses

Token exchanged successfully

Bodyapplication/json
successboolean
Example: true
dataobject
Response
application/json
{ "success": true, "data": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 1209600 } }

Get user dashboard

Request

Returns the authenticated traveler's dashboard data: active packages, data usage, unclaimed packages, and available actions.

Called automatically by the Hubby WebView — partners do not call this endpoint directly. Documented for transparency and to aid debugging of WebView network traffic.

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

curl -i -X GET \
  https://docs.hubbyesim.com/_mock/apis/openapi/webapp/me/dashboard \
  -H 'Authorization: Bearer <YOUR_JWT_HERE>'

Responses

Dashboard data

Bodyapplication/json
successboolean
Example: true
dataobject

Dashboard payload including active packages, usage, and unclaimed packages. Structure may evolve — the WebView renders this automatically.

Response
application/json
{ "success": true, "data": { "unclaimed_packages": [], "active_packages": [], "expired_packages": [] } }

Refresh a user's universal eSIM

Request

Assigns a fresh universal eSIM to a user identified by external_user_id. If the user already has a universal eSIM, it is replaced with a new one. If the user does not yet have one, a new eSIM is provisioned and assigned.

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

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

Bodyapplication/jsonrequired
external_user_idstringrequired

The partner's own user identifier. Must match the external_user_id used during authentication.

Example: "partner_user_456"
curl -i -X POST \
  https://docs.hubbyesim.com/_mock/apis/openapi/webapp/refresh-esim \
  -H 'Authorization: Bearer <YOUR_JWT_HERE>' \
  -H 'Content-Type: application/json' \
  -d '{
    "external_user_id": "partner_user_456"
  }'

Responses

Universal eSIM assigned successfully

Bodyapplication/json
successboolean
Example: true
dataobject
Response
application/json
{ "success": true, "data": { "iccid": "8901234567890123456", "qr": "LPA:1$smdp.example.com$ACTIVATION-CODE", "status": "RELEASED" } }

Native eSIM Integration

Operations

Webhooks

Webhooks