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

Native eSIM Integration

Operations

Webhooks

Webhooks

package.usage.50_percentWebhook

Request

Triggered per package when 50% of the package's data has been consumed (data-limited) or 50% of the package's duration has elapsed (time-limited).

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "package.usage.50_percent"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Per-package usage event data. These events are emitted per package, not per eSIM. For data-limited packages, thresholds are based on data consumption. For time-limited packages, thresholds are based on duration elapsed. (The time-limited package type is deprecated; duration-elapsed thresholds also apply to unlimited packages.)

data.​external_user_idstring

Partner's own user identifier

Example: "partner_user_456"
data.​booking_idstring or null

The booking that granted this package, when it was delivered from a booking. For top-ups on promo-code-delivered eSIMs this carries the booking the eSIM originated from. Null for packages the traveler purchased themselves on web-app eSIMs (webview shop purchases and top-ups) — those are not tied to a booking; attribute them to the traveler via external_user_id.

Example: "booking_abc"
data.​package_idstring

The specific package that triggered the event — the same identifier sent as package_id on package.activated and topup.completed

Example: "pkg_xyz"
data.​package_queue_uuidstring or null

The package_queues[].uuid from the booking response that granted this package, when the booking was delivered via package queues. Also present for packages the traveler purchased in the webview shop (those are delivered via a queue too). Null otherwise — see promo_code_id for promo-code bookings; both are null for direct top-ups and packages provisioned before this field was introduced.

Example: "b29bd4d7-f058-497d-bc7b-ada1c4fad0dd"
data.​promo_code_idstring or null

The promo code that granted this package, when the booking was delivered via promo codes. Matches promo_codes[].promo_code in the booking response. Null otherwise — see package_queue_uuid for package-queue bookings.

Example: "SUMMER2026GR"
data.​destinationstring

ISO country code

Example: "GR"
data.​sizestring

Package size (data-limited packages only)

Example: "1GB"
data.​package_typestring

Type of package

Enum"data-limited""time-limited""unlimited""starter"
Example: "data-limited"
data.​used_bytesinteger

Bytes consumed so far (data-limited packages)

Example: 858993459
data.​remaining_bytesinteger

Bytes remaining (data-limited packages)

Example: 214748365
data.​duration_daysinteger

Total package validity in days (duration-based packages — unlimited, and the deprecated time-limited)

Example: 30
data.​elapsed_daysinteger

Days elapsed since activation (duration-based packages — unlimited, and the deprecated time-limited)

Example: 24
data.​remaining_daysinteger

Days remaining (duration-based packages — unlimited, and the deprecated time-limited)

Example: 6
data.​usage_percentinteger

Usage percentage that triggered this event (50, 80, or 100). Represents data consumed (data-limited) or duration elapsed (time-limited).

Example: 80
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "package.usage.50_percent:pkg_xyz"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "package.usage.50_percent", "timestamp": "2019-08-24T14:15:22Z", "data": { "external_user_id": "partner_user_456", "booking_id": "booking_abc", "package_id": "pkg_xyz", "package_queue_uuid": "b29bd4d7-f058-497d-bc7b-ada1c4fad0dd", "promo_code_id": "SUMMER2026GR", "destination": "GR", "size": "1GB", "package_type": "data-limited", "used_bytes": 858993459, "remaining_bytes": 214748365, "duration_days": 30, "elapsed_days": 24, "remaining_days": 6, "usage_percent": 80 }, "event_id": "package.usage.50_percent:pkg_xyz", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully

package.usage.80_percentWebhook

Request

Triggered per package when 80% of the package's data has been consumed (data-limited) or 80% of the package's duration has elapsed (time-limited). This is the most effective trigger for top-up conversion.

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "package.usage.80_percent"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Per-package usage event data. These events are emitted per package, not per eSIM. For data-limited packages, thresholds are based on data consumption. For time-limited packages, thresholds are based on duration elapsed. (The time-limited package type is deprecated; duration-elapsed thresholds also apply to unlimited packages.)

data.​external_user_idstring

Partner's own user identifier

Example: "partner_user_456"
data.​booking_idstring or null

The booking that granted this package, when it was delivered from a booking. For top-ups on promo-code-delivered eSIMs this carries the booking the eSIM originated from. Null for packages the traveler purchased themselves on web-app eSIMs (webview shop purchases and top-ups) — those are not tied to a booking; attribute them to the traveler via external_user_id.

Example: "booking_abc"
data.​package_idstring

The specific package that triggered the event — the same identifier sent as package_id on package.activated and topup.completed

Example: "pkg_xyz"
data.​package_queue_uuidstring or null

The package_queues[].uuid from the booking response that granted this package, when the booking was delivered via package queues. Also present for packages the traveler purchased in the webview shop (those are delivered via a queue too). Null otherwise — see promo_code_id for promo-code bookings; both are null for direct top-ups and packages provisioned before this field was introduced.

Example: "b29bd4d7-f058-497d-bc7b-ada1c4fad0dd"
data.​promo_code_idstring or null

The promo code that granted this package, when the booking was delivered via promo codes. Matches promo_codes[].promo_code in the booking response. Null otherwise — see package_queue_uuid for package-queue bookings.

Example: "SUMMER2026GR"
data.​destinationstring

ISO country code

Example: "GR"
data.​sizestring

Package size (data-limited packages only)

Example: "1GB"
data.​package_typestring

Type of package

Enum"data-limited""time-limited""unlimited""starter"
Example: "data-limited"
data.​used_bytesinteger

Bytes consumed so far (data-limited packages)

Example: 858993459
data.​remaining_bytesinteger

Bytes remaining (data-limited packages)

Example: 214748365
data.​duration_daysinteger

Total package validity in days (duration-based packages — unlimited, and the deprecated time-limited)

Example: 30
data.​elapsed_daysinteger

Days elapsed since activation (duration-based packages — unlimited, and the deprecated time-limited)

Example: 24
data.​remaining_daysinteger

Days remaining (duration-based packages — unlimited, and the deprecated time-limited)

Example: 6
data.​usage_percentinteger

Usage percentage that triggered this event (50, 80, or 100). Represents data consumed (data-limited) or duration elapsed (time-limited).

Example: 80
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "package.usage.80_percent:pkg_xyz"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "package.usage.80_percent", "timestamp": "2019-08-24T14:15:22Z", "data": { "external_user_id": "partner_user_456", "booking_id": "booking_abc", "package_id": "pkg_xyz", "package_queue_uuid": "b29bd4d7-f058-497d-bc7b-ada1c4fad0dd", "promo_code_id": "SUMMER2026GR", "destination": "GR", "size": "1GB", "package_type": "data-limited", "used_bytes": 858993459, "remaining_bytes": 214748365, "duration_days": 30, "elapsed_days": 24, "remaining_days": 6, "usage_percent": 80 }, "event_id": "package.usage.80_percent:pkg_xyz", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully

package.usage.100_percentWebhook

Request

Triggered per package when 100% of the package's data has been consumed (data-limited) or the full package duration has elapsed (time-limited).

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "package.usage.100_percent"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Per-package usage event data. These events are emitted per package, not per eSIM. For data-limited packages, thresholds are based on data consumption. For time-limited packages, thresholds are based on duration elapsed. (The time-limited package type is deprecated; duration-elapsed thresholds also apply to unlimited packages.)

data.​external_user_idstring

Partner's own user identifier

Example: "partner_user_456"
data.​booking_idstring or null

The booking that granted this package, when it was delivered from a booking. For top-ups on promo-code-delivered eSIMs this carries the booking the eSIM originated from. Null for packages the traveler purchased themselves on web-app eSIMs (webview shop purchases and top-ups) — those are not tied to a booking; attribute them to the traveler via external_user_id.

Example: "booking_abc"
data.​package_idstring

The specific package that triggered the event — the same identifier sent as package_id on package.activated and topup.completed

Example: "pkg_xyz"
data.​package_queue_uuidstring or null

The package_queues[].uuid from the booking response that granted this package, when the booking was delivered via package queues. Also present for packages the traveler purchased in the webview shop (those are delivered via a queue too). Null otherwise — see promo_code_id for promo-code bookings; both are null for direct top-ups and packages provisioned before this field was introduced.

Example: "b29bd4d7-f058-497d-bc7b-ada1c4fad0dd"
data.​promo_code_idstring or null

The promo code that granted this package, when the booking was delivered via promo codes. Matches promo_codes[].promo_code in the booking response. Null otherwise — see package_queue_uuid for package-queue bookings.

Example: "SUMMER2026GR"
data.​destinationstring

ISO country code

Example: "GR"
data.​sizestring

Package size (data-limited packages only)

Example: "1GB"
data.​package_typestring

Type of package

Enum"data-limited""time-limited""unlimited""starter"
Example: "data-limited"
data.​used_bytesinteger

Bytes consumed so far (data-limited packages)

Example: 858993459
data.​remaining_bytesinteger

Bytes remaining (data-limited packages)

Example: 214748365
data.​duration_daysinteger

Total package validity in days (duration-based packages — unlimited, and the deprecated time-limited)

Example: 30
data.​elapsed_daysinteger

Days elapsed since activation (duration-based packages — unlimited, and the deprecated time-limited)

Example: 24
data.​remaining_daysinteger

Days remaining (duration-based packages — unlimited, and the deprecated time-limited)

Example: 6
data.​usage_percentinteger

Usage percentage that triggered this event (50, 80, or 100). Represents data consumed (data-limited) or duration elapsed (time-limited).

Example: 80
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "package.usage.100_percent:pkg_xyz"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "package.usage.100_percent", "timestamp": "2019-08-24T14:15:22Z", "data": { "external_user_id": "partner_user_456", "booking_id": "booking_abc", "package_id": "pkg_xyz", "package_queue_uuid": "b29bd4d7-f058-497d-bc7b-ada1c4fad0dd", "promo_code_id": "SUMMER2026GR", "destination": "GR", "size": "1GB", "package_type": "data-limited", "used_bytes": 858993459, "remaining_bytes": 214748365, "duration_days": 30, "elapsed_days": 24, "remaining_days": 6, "usage_percent": 80 }, "event_id": "package.usage.100_percent:pkg_xyz", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully

esim.installedWebhook

Request

Triggered when the traveler has successfully installed the eSIM on their device.

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "esim.installed"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Event-specific payload

data.​external_user_idstring

Partner's own user identifier

Example: "partner_user_456"
data.​booking_idstring or null

The booking this eSIM originated from, for promo-code-delivered eSIMs. Null for eSIMs delivered via package queues — those are shared across the traveler's bookings, so no single booking applies; use package_queue_uuid on the package-level events for per-booking attribution.

Example: "booking_abc"
data.​iccidstring

ICCID of the eSIM

Example: "8901234567890123456"
data.​promo_code_idstring or null

The promo code that granted this eSIM's package, when the booking was delivered via promo codes. Matches promo_codes[].promo_code in the booking response. Null for eSIMs without a promo origin.

Example: "SUMMER2026GR"
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "esim.installed:abc123"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "esim.installed", "timestamp": "2019-08-24T14:15:22Z", "data": { "external_user_id": "partner_user_456", "booking_id": "booking_abc", "iccid": "8901234567890123456", "promo_code_id": "SUMMER2026GR" }, "event_id": "esim.installed:abc123", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully

esim.removedWebhook

Request

Triggered when the traveler has removed the eSIM from their device. A removed eSIM can be re-installed — this event does not mean permanent loss of access.

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "esim.removed"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Event-specific payload

data.​external_user_idstring

Partner's own user identifier

Example: "partner_user_456"
data.​booking_idstring or null

The booking this eSIM originated from, for promo-code-delivered eSIMs. Null for eSIMs delivered via package queues — those are shared across the traveler's bookings, so no single booking applies; use package_queue_uuid on the package-level events for per-booking attribution.

Example: "booking_abc"
data.​iccidstring

ICCID of the eSIM

Example: "8901234567890123456"
data.​promo_code_idstring or null

The promo code that granted this eSIM's package, when the booking was delivered via promo codes. Matches promo_codes[].promo_code in the booking response. Null for eSIMs without a promo origin.

Example: "SUMMER2026GR"
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "esim.removed:abc123"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "esim.removed", "timestamp": "2019-08-24T14:15:22Z", "data": { "external_user_id": "partner_user_456", "booking_id": "booking_abc", "iccid": "8901234567890123456", "promo_code_id": "SUMMER2026GR" }, "event_id": "esim.removed:abc123", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully

package.activatedWebhook

Request

Triggered when a package has been activated and the traveler can use data. Typically occurs when the traveler arrives at the destination and connects to a local network.

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "package.activated"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Event-specific payload

data.​external_user_idstring

Partner's own user identifier

Example: "partner_user_456"
data.​booking_idstring or null

The booking that granted this package, when it was delivered from a booking. For top-ups on promo-code-delivered eSIMs this carries the booking the eSIM originated from. Null for packages the traveler purchased themselves on web-app eSIMs (webview shop purchases and top-ups) — attribute those to the traveler via external_user_id.

Example: "booking_abc"
data.​package_idstring

Identifier of the provisioned package on the eSIM. Assigned when the package is created at redemption — distinct from the package_queues[].uuid returned at booking creation (see package_queue_uuid). The same identifier appears on the package.usage.* events for this package.

Example: "pkg_xyz"
data.​package_queue_uuidstring or null

The package_queues[].uuid from the booking response that granted this package, when the booking was delivered via package queues. Use it to match this event to the package you created. Also present for packages the traveler purchased in the webview shop (those are delivered via a queue too). Null otherwise — see promo_code_id for promo-code bookings; both are null for direct top-ups and packages provisioned before this field was introduced.

Example: "b29bd4d7-f058-497d-bc7b-ada1c4fad0dd"
data.​promo_code_idstring or null

The promo code that granted this package, when the booking was delivered via promo codes. Matches promo_codes[].promo_code in the booking response. Use it to match this event to the package you created. Null otherwise — see package_queue_uuid for package-queue bookings.

Example: "SUMMER2026GR"
data.​destinationstring

ISO country code

Example: "GR"
data.​sizestring

Package size

Example: "1GB"
data.​activated_atstring(date-time)

When the package was activated

Example: "2026-07-15T16:00:00Z"
data.​expires_atstring(date-time)

When the package expires

Example: "2027-07-15T16:00:00Z"
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "package.activated:pkg_xyz"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "package.activated", "timestamp": "2019-08-24T14:15:22Z", "data": { "external_user_id": "partner_user_456", "booking_id": "booking_abc", "package_id": "pkg_xyz", "package_queue_uuid": "b29bd4d7-f058-497d-bc7b-ada1c4fad0dd", "promo_code_id": "SUMMER2026GR", "destination": "GR", "size": "1GB", "activated_at": "2026-07-15T16:00:00Z", "expires_at": "2027-07-15T16:00:00Z" }, "event_id": "package.activated:pkg_xyz", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully

promo_code.redeemedWebhook

Request

Triggered when a traveler successfully redeems a promo code.

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "promo_code.redeemed"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Event-specific payload

data.​promocodestring

The promo code that was redeemed

Example: "SUMMER2026GR"
data.​booking_idstring

Booking ID

Example: "booking_abc"
data.​redeemed_atstring(date-time)

When the promo code was redeemed

Example: "2026-07-10T12:00:00Z"
data.​redeemed_bystring or null

Email address of the user who redeemed the promo code. Null when the email cannot be resolved.

Example: "user@example.com"
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "promo_code.redeemed:SUMMER2026GR"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "promo_code.redeemed", "timestamp": "2019-08-24T14:15:22Z", "data": { "promocode": "SUMMER2026GR", "booking_id": "booking_abc", "redeemed_at": "2026-07-10T12:00:00Z", "redeemed_by": "user@example.com" }, "event_id": "promo_code.redeemed:SUMMER2026GR", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully

booking.within_cutoffWebhook

Request

Triggered when the booking's departure date enters the configured cutoff window (default: 7 days). This is the optimal time for travelers to install their eSIM while still at home on Wi-Fi. The esim_installed field indicates whether the traveler has already installed — use it to decide whether to send an installation reminder or a confirmation.

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "booking.within_cutoff"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Event-specific payload

data.​external_user_idstring

Partner's own user identifier

Example: "partner_user_456"
data.​booking_idstring

Booking ID

Example: "booking_abc"
data.​departure_datestring(date-time)

ISO 8601 departure datetime with timezone

Example: "2026-07-15T14:30:00+02:00"
data.​days_until_departureinteger

Days remaining until departure

Example: 7
data.​esim_installedboolean

Whether the traveler has installed their eSIM

Example: false
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "booking.within_cutoff:booking_abc"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "booking.within_cutoff", "timestamp": "2019-08-24T14:15:22Z", "data": { "external_user_id": "partner_user_456", "booking_id": "booking_abc", "departure_date": "2026-07-15T14:30:00+02:00", "days_until_departure": 7, "esim_installed": false }, "event_id": "booking.within_cutoff:booking_abc", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully

booking.about_to_departWebhook

Request

Triggered when departure is imminent (default: 2 hours before the time in departure_date). This is the last-chance reminder for travelers who haven't installed their eSIM. Requires a full datetime with timezone in departure_date (e.g., 2026-07-15T14:30:00+02:00) — if only a date string was provided, this event cannot fire.

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "booking.about_to_depart"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Event-specific payload

data.​external_user_idstring

Partner's own user identifier

Example: "partner_user_456"
data.​booking_idstring

Booking ID

Example: "booking_abc"
data.​departure_datestring(date-time)

ISO 8601 departure datetime with timezone

Example: "2026-07-15T14:30:00+02:00"
data.​hours_until_departureinteger

Hours remaining until departure

Example: 2
data.​esim_installedboolean

Whether the traveler has installed their eSIM

Example: false
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "booking.about_to_depart:booking_abc"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "booking.about_to_depart", "timestamp": "2019-08-24T14:15:22Z", "data": { "external_user_id": "partner_user_456", "booking_id": "booking_abc", "departure_date": "2026-07-15T14:30:00+02:00", "hours_until_departure": 2, "esim_installed": false }, "event_id": "booking.about_to_depart:booking_abc", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully

topup.completedWebhook

Request

Triggered when a traveler successfully completes a top-up payment.

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "topup.completed"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Event-specific payload

data.​external_user_idstring or null

Your identifier for the traveler

Example: "partner_user_456"
data.​booking_idstring or null

The booking this eSIM originated from, for promo-code-delivered eSIMs. Null for top-ups on eSIMs delivered via package queues — a top-up adds data to the traveler's eSIM, which is shared across bookings, so it is not tied to a single booking; attribute top-ups to the traveler via external_user_id.

Example: null
data.​iccidstring

The eSIM ICCID

Example: "8901234567890123456"
data.​payment_idstring

Unique identifier for this payment

Example: "payment_xyz"
data.​amountinteger

Amount charged in the smallest currency unit (e.g. cents)

Example: 1200
data.​currencystring

ISO 4217 currency code

Example: "EUR"
data.​promo_code_idstring or null

Promo code applied to this top-up, or null

Example: null
data.​package_idstring or null

Identifier of the package the top-up added to the eSIM. The same identifier appears as package_id on the package.activated and package.usage.* webhooks — store it together with payment_id to track this package across events. Null if the package cannot be resolved.

Example: "pkg_xyz"
data.​destinationstring or null

ISO country code of the topped-up package. Null for legacy packages without a stored destination.

Example: "GR"
data.​sizestring or null

Data allowance of the topped-up package (e.g. "1GB"). Null when the package size is unknown.

Example: "1GB"
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "topup.completed:pi_3OabcdEfGhIjKlMn"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "topup.completed", "timestamp": "2019-08-24T14:15:22Z", "data": { "external_user_id": "partner_user_456", "booking_id": null, "iccid": "8901234567890123456", "payment_id": "payment_xyz", "amount": 1200, "currency": "EUR", "promo_code_id": null, "package_id": "pkg_xyz", "destination": "GR", "size": "1GB" }, "event_id": "topup.completed:pi_3OabcdEfGhIjKlMn", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully

classic_package_queue.claimedWebhook

Request

Triggered when a traveler claims a classic-eSIM grant via the native app — the moment after claim_classic_package_queue provisions (or tops up onto a matching existing) eSIM and marks the PackageQueue as claimed.

Distinct from topup.completed: no payment is involved here, and the is_top_up flag in the payload only indicates whether the package was attached to an existing matching eSIM (cost optimisation) — it does not imply a billing event.

Opt-in: enable via webhook_settings.events.classic_package_queue_claimed = true.

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "classic_package_queue.claimed"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Payload for the classic_package_queue.claimed webhook — fires when a traveler claims a classic-eSIM grant via the native app. Distinct from topup.completed (which is for Stripe-paid additions only); no money changes hands here.

data.​queue_idstring

Stable identifier for the PackageQueue that was just claimed. Matches the uuid returned in BookingResponse.package_queues[] when the Booking was created.

Example: "fe17e0ef-0cd3-4c2f-8e27-8cbaca0bde29"
data.​booking_idstring or null

The originating Booking. Always present for grants created via POST /bookings; may be null for grants ingested via other channels in the future.

Example: "iVlU7xgTCUq0537I9GpH"
data.​esim_iccidstring

ICCID of the eSIM the package was activated on — either a freshly provisioned one or an existing one that matched on destination + IMSI (top-up branch).

Example: "8901234567890123456"
data.​user_idstring

Hubby's User identifier (Firebase uid). Same user_id that calls the claim_classic_package_queue onCall.

Example: "YMQsyXIUet1q2L1Z76TJwoO7QQqP"
data.​partner_idstring

Your Partner identifier.

Example: "partner-1"
data.​is_top_upboolean

true when the new EsimPackage was attached to a User's existing matching eSIM (same destination + same IMSI); false when a fresh ICCID was provisioned. Informational only — the User's experience is the same either way. This is not the same concept as the topup.completed event; that one is reserved for Stripe-paid additions.

Example: false
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "classic_package_queue.claimed:fe17e0ef-0cd3-4c2f-8e27-8cbaca0bde29"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "classic_package_queue.claimed", "timestamp": "2019-08-24T14:15:22Z", "data": { "queue_id": "fe17e0ef-0cd3-4c2f-8e27-8cbaca0bde29", "booking_id": "iVlU7xgTCUq0537I9GpH", "esim_iccid": "8901234567890123456", "user_id": "YMQsyXIUet1q2L1Z76TJwoO7QQqP", "partner_id": "partner-1", "is_top_up": false }, "event_id": "classic_package_queue.claimed:fe17e0ef-0cd3-4c2f-8e27-8cbaca0bde29", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully

package.purchasedWebhook

Request

Triggered when an attributed traveler buys a NEW eSIM — the first purchase for a given destination — through the Hubby app or web app. Every later purchase of more data for a destination the traveler already has fires topup.completed instead, so you receive exactly one event per purchase: package.purchased for the first, topup.completed for each subsequent one.

Not fired for: top-ups on existing eSIMs (use topup.completed), first-time promo redemptions (use promo_code.redeemed), or purchases by travelers with no partner attribution.

Correlation: web-app purchases carry package_queue_uuid — the stable handle that reappears on package.activated and package.usage.* once the package is provisioned. package_id is not present on this event because the provisioned package does not exist yet at payment time; capture it from package.activated.

Bodyapplication/json
eventstringrequired

Event type identifier

Example: "package.purchased"
timestampstring(date-time)required

ISO 8601 UTC timestamp of when the event occurred. This is a body field for convenience and is distinct from the x-hubby-timestamp signing header (epoch seconds). On a replay this reproduces the original event time.

dataobjectrequired

Event-specific payload

data.​external_user_idstring or null

Your identifier for the traveler, captured when the original promo code was created

Example: "partner_user_456"
data.​booking_idstring or null

The booking this eSIM originated from, for promo-code-delivered eSIMs. Null for purchases made in the webview shop — those are traveler-initiated and not tied to a booking; attribute them to the traveler via external_user_id.

Example: null
data.​iccidstring

The newly purchased eSIM ICCID

Example: "8901234567890123456"
data.​payment_idstring

Unique identifier for this payment

Example: "pi_3OabcdEfGhIjKlMn"
data.​amountinteger

Amount charged in the smallest currency unit (e.g. cents)

Example: 3000
data.​currencystring

ISO 4217 currency code

Example: "eur"
data.​promo_code_idstring or null

Original promo code that started the partner attribution, when one exists

Example: "SUMMER2026GR"
data.​package_queue_uuidstring or null

Identifier of the package queue entry this purchase created, for purchases made in the web app — the same value that later appears as package_queue_uuid on the package.activated and package.usage.* events for this package, so store it to join the purchase to the rest of the package lifecycle. For booking-granted packages this matches the package_queues[].uuid from the booking response. Null for purchases made in the native app, where the purchase creates a new eSIM per destination and iccid links the events instead.

Example: "b29bd4d7-f058-497d-bc7b-ada1c4fad0dd"
event_idstringrequired

Stable identifier for the logical event, also sent as the x-hubby-event-id header. Identical across retries and replays of the same event — deduplicate on it. Derived deterministically as {event}:{entity_id}.

Example: "package.purchased:pi_3OabcdEfGhIjKlMn"
delivery_idstringrequired

Identifier for this single delivery, also sent as the x-hubby-delivery-id header. A replay of the same event carries the same event_id but a new delivery_id.

Example: "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77"
application/json
{ "event": "package.purchased", "timestamp": "2019-08-24T14:15:22Z", "data": { "external_user_id": "partner_user_456", "booking_id": null, "iccid": "8901234567890123456", "payment_id": "pi_3OabcdEfGhIjKlMn", "amount": 3000, "currency": "eur", "promo_code_id": "SUMMER2026GR", "package_queue_uuid": "b29bd4d7-f058-497d-bc7b-ada1c4fad0dd" }, "event_id": "package.purchased:pi_3OabcdEfGhIjKlMn", "delivery_id": "dlv_2f1c8e9a-7b3d-4a52-9c10-1e6b2f0a4d77" }

Responses

Webhook received successfully