Basic Booking Integration Guide
This tutorial will guide you through creating eSIM bookings using the Hubby API, with a comprehensive explanation of package_specifications and how to use them effectively.
Overview
Creating a booking with the Hubby API involves:
- Understanding package_specifications structure
- Choosing the right package type for your use case
- Handling different booking scenarios
- Implementing proper error handling and validation
API Endpoints
Hubby provides two API endpoints for different environments:
- Production:
https://api.hubbyesim.com/api- Use this for live applications - Staging:
https://api-staging.hubby.dev/api- Use this for testing and development
Always test thoroughly in staging before using production.
Understanding Package Specifications
The package_specifications field is the core of any booking request. It's an array of objects that define what eSIM packages should be created for the customer. For each entry, provide either package_id (direct reference) or other properties (destination, size, package_type, etc.). When package_id is present, the package is resolved solely by that identifier—other fields in the same specification are not used for resolution and are effectively ignored.
Package Specification Structure
interface PackageSpecification {
// First determine the type of package
package_type?: 'data-limited' | 'time-limited' | 'starter' | 'unlimited';
// Direct package selection
package_id?: string;
// Destination-based selection
destination?: string; // ISO2, ISO3, or continent code (e.g., "US", "USA", "EU")
// Size-based selection
size?: string; // Package size (e.g., "500MB", "1GB", "3GB", "5GB", "10GB", "20GB")
// Package type and duration
package_type?: 'data-limited' | 'time-limited' | 'starter' | 'unlimited';
package_duration?: number; // Duration in days
}Package Selection Methods
There are three main ways to specify packages:
1. Destination + Size Selection
Specify destination and size to let the system find the best matching package:
{
"package_specifications": [
{
"destination": "USA",
"size": "1GB",
"package_type": "starter"
}
]
}2. Destination-Only Selection
Provide only destination and let our system determine the package based on settings on our platform (this is setup on our end, when we create your contract) determine the best package:
{
"package_specifications": [
{
"destination": "EU",
"package_type": "data-limited"
}
]
}3. Direct Package Selection
Use package_id when you know the exact package you want to provide. When package_id is set, that specification is resolved only by this identifier; any other fields (destination, size, package_type, etc.) in the same entry are ignored for resolution.
{
"package_specifications": [
{
"package_id": "pkg_12345"
}
]
}Package Types Explained
The Hubby API supports four different package types, each designed for specific use cases:
1. Data-Limited Packages (Default)
Package Type: data-limited
Default Duration: 365 days
Traditional eSIM packages with a specific data allowance that can be used over an extended period.
Characteristics:
- Fixed data allowance (e.g., 1GB, 5GB, 10GB)
- 365 days validity by default
- Data is consumed as the user browses, streams, or uses apps
- Package expires when either data is exhausted OR time limit is reached
Use Cases:
- Long-term travelers
- Business travelers who need predictable data usage
- Budget-conscious users
- Most common scenarios (default package type)
Example of the full specification: part of this can be setup on our end, so that you only need to specify the destination.
{
"package_specifications": [
{
"destination": "USA",
"size": "1GB",
"package_type": "data-limited",
"package_duration": 365
}
]
}2. Time-Limited Packages
Package Type: time-limited
Default Duration: 2 days
Full-speed data packages with both a maximum data allowance and a maximum time limit.
Characteristics:
- Full-speed data access
- Maximum data allowance (e.g., 5GB, 10GB)
- Maximum time period (typically 1-30 days)
- Expires when either data limit OR time limit is reached
- Data consumption is tracked
Use Cases:
- Short business trips with high data needs
- Weekend getaways
- Users who need full-speed access
- Scenarios requiring both data and time limits
Example:
{
"package_specifications": [
{
"destination": "EU",
"size": "5GB",
"package_type": "time-limited",
"package_duration": 7
}
]
}3. Starter Packages
Package Type: starter
Default Duration: 2 days
Both data and time limited packages, providing a maximum data allowance within a specific time period.
Characteristics:
- Limited data allowance
- Limited time period
- Expires when either limit is reached
- Good for testing or light usage
Use Cases:
- First-time eSIM users
- Light data users
- Testing connectivity
- Backup connectivity
Example:
{
"package_specifications": [
{
"destination": "JP",
"size": "500MB",
"package_type": "starter",
"package_duration": 3
}
]
}4. Unlimited Packages
Package Type: unlimited
Default Duration: Varies by destination
Unlimited data packages with time-based expiration and fair use policy.
Characteristics:
- Unlimited data usage
- Time-based expiration only
- Fair use policy applies
- No data consumption tracking
- Full-speed access
Use Cases:
- Long-term travelers
- Heavy data users
- Business travelers
- Users who need unrestricted access
Example:
{
"package_specifications": [
{
"destination": "USA",
"package_type": "unlimited",
"package_duration": 30
}
]
}Creating a Booking
Now let's see how to create a complete booking using the Hubby API:
Basic Booking Request Structure
interface BookingRequest {
// Required fields
departure_date: string; // ISO 8601 date string
communication_options: {
should_send_message: boolean;
channels: ('EMAIL' | 'WHATSAPP' | 'PUSH_NOTIFICATION' | 'SMS')[];
};
package_specifications: PackageSpecification[]; // At least one required
// Customer information (at least one of email or booking_id required)
email?: string;
booking_id?: string;
// Optional customer details
first_name?: string;
last_name?: string;
full_name?: string;
phone?: string; // E.164 format (+1234567890)
title?: 'mr.' | 'ms.' | 'mrs.' | 'miss' | 'dr.' | 'prof.';
gender?: 'M' | 'F' | 'O';
// Travel details
return_date?: string; // ISO 8601 date string
flight_number?: string;
pax?: number; // Number of passengers
departure_location?: string; // Departure location (e.g., airport code, city)
// Branding and customization
custom_branding?: string; // Sets app styling (must exist in Hubby platform)
// Additional options
locale?: string; // e.g., 'en', 'de', 'fr'
external_id?: string; // Your internal booking reference
data?: object; // Custom metadata
}Example: Simple Single Package Booking
const createSimpleBooking = async () => {
const bookingRequest = {
departure_date: "2026-01-03",
email: "customer@example.com",
first_name: "John",
last_name: "Doe",
communication_options: {
should_send_message: true,
channels: ["EMAIL"]
},
package_specifications: [
{
destination: "US",
size: "1GB",
package_type: "data-limited"
}
]
};
// Use staging for development, production for live
const API_BASE_URL = process.env.NODE_ENV === 'production'
? 'https://api.hubbyesim.com'
: 'https://api-staging.hubby.dev';
try {
const response = await fetch(`${API_BASE_URL}/api/bookings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify(bookingRequest)
});
const booking = await response.json();
console.log('Booking created:', booking);
return booking;
} catch (error) {
console.error('Booking failed:', error);
throw error;
}
};Example: Multi-Destination Booking
const createMultiDestinationBooking = async () => {
const bookingRequest = {
departure_date: "2026-01-03",
return_date: "2026-01-03",
email: "traveler@example.com",
first_name: "Jane",
last_name: "Smith",
phone: "+1234567890",
flight_number: "AA123",
communication_options: {
should_send_message: true,
channels: ["EMAIL", "WHATSAPP"]
},
package_specifications: [
{
destination: "US",
size: "2GB",
package_type: "data-limited"
},
{
destination: "EU",
size: "1GB",
package_type: "data-limited"
}
],
locale: "en",
external_id: "booking_12345",
data: {
source: "travel_agency",
campaign: "winter_travel"
}
};
const response = await fetch('https://api.hubbyesim.com/api/bookings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify(bookingRequest)
});
return await response.json();
};Example: Time-Limited Package for Short Trip
const createShortTripBooking = async () => {
const bookingRequest = {
departure_date: "2026-01-03",
return_date: "2026-01-03",
email: "business@example.com",
first_name: "Mike",
last_name: "Johnson",
communication_options: {
should_send_message: true,
channels: ["EMAIL"]
},
package_specifications: [
{
destination: "UK",
size: "3GB",
package_type: "time-limited",
package_duration: 3
}
]
};
const response = await fetch('https://api.hubbyesim.com/api/bookings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify(bookingRequest)
});
return await response.json();
};Example: Unlimited Package for Long Trip
const createUnlimitedBooking = async () => {
const bookingRequest = {
departure_date: "2026-01-03",
return_date: "2026-01-03",
email: "traveler@example.com",
first_name: "Alex",
last_name: "Brown",
communication_options: {
should_send_message: true,
channels: ["EMAIL"]
},
package_specifications: [
{
destination: "United states",
package_type: "unlimited",
package_duration: 30
}
]
};
const response = await fetch('https://api.hubbyesim.com/api/bookings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify(bookingRequest)
});
return await response.json();
};Example: Using Direct Package ID (only in very specific cases)
const createDirectPackageBooking = async () => {
const bookingRequest = {
departure_date: "2026-01-03",
email: "customer@example.com",
first_name: "Sarah",
last_name: "Wilson",
communication_options: {
should_send_message: true,
channels: ["EMAIL"]
},
package_specifications: [
{
package_id: "pkg_premium_us_5gb"
}
]
};
const response = await fetch('https://api.hubbyesim.com/api/bookings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify(bookingRequest)
});
return await response.json();
};Error Handling and Validation
Proper error handling is crucial for a robust integration. Here are common scenarios and how to handle them:
Common Validation Errors
const validateBookingRequest = (request) => {
const errors = [];
// Required fields validation
if (!request.departure_date) {
errors.push('departure_date is required');
}
if (!request.package_specifications || request.package_specifications.length === 0) {
errors.push('At least one package_specification is required');
}
if (!request.email && !request.booking_id) {
errors.push('Either email or booking_id is required');
}
if (!request.communication_options) {
errors.push('communication_options is required');
}
// Package specification validation
request.package_specifications?.forEach((spec, index) => {
if (!spec.package_id && !spec.destination && !spec.size) {
errors.push(`package_specifications[${index}]: At least one of package_id, destination, or size is required`);
}
if (spec.package_type === 'time-limited' && !spec.package_duration) {
errors.push(`package_specifications[${index}]: package_duration is required for time-limited packages`);
}
if (spec.package_type === 'starter' && !spec.package_duration) {
errors.push(`package_specifications[${index}]: package_duration is required for starter packages`);
}
if (spec.package_type === 'unlimited' && !spec.package_duration) {
errors.push(`package_specifications[${index}]: package_duration is required for unlimited packages`);
}
});
return errors;
};API Error Handling
const createBookingWithErrorHandling = async (bookingRequest) => {
try {
// Validate request before sending
const validationErrors = validateBookingRequest(bookingRequest);
if (validationErrors.length > 0) {
throw new Error(`Validation failed: ${validationErrors.join(', ')}`);
}
const response = await fetch('https://api.hubbyesim.com/api/bookings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify(bookingRequest)
});
if (!response.ok) {
const errorData = await response.json();
switch (response.status) {
case 400:
throw new Error(`Bad Request: ${errorData.message || 'Invalid request data'}`);
case 401:
throw new Error('Unauthorized: Invalid API key');
case 403:
throw new Error('Forbidden: Insufficient permissions');
case 404:
throw new Error('Not Found: Package or destination not available');
case 422:
throw new Error(`Validation Error: ${errorData.message || 'Invalid package specification'}`);
case 429:
throw new Error('Rate Limited: Too many requests');
case 500:
throw new Error('Server Error: Please try again later');
default:
throw new Error(`API Error: ${response.status} - ${errorData.message || 'Unknown error'}`);
}
}
const booking = await response.json();
return booking;
} catch (error) {
console.error('Booking creation failed:', error);
// Handle specific error types
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new Error('Network error: Unable to connect to Hubby API');
}
throw error;
}
};Best Practices
1. Package Selection Strategy
- Use destination + size for most cases - gives flexibility while ensuring specific data amounts
- Use starter when you aim to optimize commission on topups.
- Use destination-only when you want to rely on our commercial agreement to determine the best package
2. Error Handling
- Always validate requests before sending to the API
- Provide meaningful error messages to users
- Log errors for debugging and monitoring
3. User Experience
- Show loading states during API calls
- Provide clear feedback on success/failure
- Allow users to modify package selections
- Display package details clearly
4. Destination
- Our internal format is ISO3 country
- HOWEVER, we use AI to resolve your input to a country, dont worry too much about getting an iso3 in
- There is an advanced method for combining multiple destinations into one promo. Submit an array of strings instead of a string or refer to the relevant docs.
- finally we also support issueing destinationless promos, the user will then be presented with a destination selection screen.
Common Issues and Solutions
Issue: Package Not Found
Problem: API returns 404 for a package specification Solution:
- Verify destination codes are valid (ISO2, ISO3, or continent codes)
- Check if the size format is correct (e.g., "500MB", "1GB", "3GB", "5GB", "10GB", "20GB")
- Ensure the package type is supported for the destination
Issue: Validation Errors
Problem: API returns 422 validation errors Solution:
- Ensure all required fields are present
- Check date formats (ISO 8601)
- Verify phone number format (E.164)
- Validate package specifications
Need assistance with your booking integration?
- Check our API documentation
- Review the package types guide
- Contact tech@hubbyesim.com
- Join our developer community for discussions and examples