Last updated

Starter Package

This guide demonstrates how to integrate Hubby eSIM API to create bookings with starter packages where the destination is specified upfront. This is ideal for integrations where you know the customer's travel destination at booking time.

Overview

In this integration:

  1. You create a booking with a starter package and a specific destination
  2. The system generates a promo code for that destination
  3. You provide the promo code to your customer
  4. The customer redeems the code in the Hubby app with the destination already set

Prerequisites

  • A Hubby Partner account with API access
  • Your API credentials (API key and secret key)
  • Ability to make HTTP POST requests from your system

API Endpoints

Hubby provides two API endpoints for different environments:

Production Environment

  • Base URL: https://api.hubbyesim.com
  • Use for: Live applications and production integrations
  • Full endpoint: https://api.hubbyesim.com/api/bookings

Staging Environment

  • Base URL: https://api-staging.hubby.dev
  • Use for: Testing, development, and integration testing
  • Full endpoint: https://api-staging.hubby.dev/api/bookings

When to Use Each Environment

Use Staging (api-staging.hubby.dev) when:

  • Developing and testing your integration
  • Verifying authentication and request formats
  • Testing booking creation workflows
  • Debugging integration issues

Use Production (api.hubbyesim.com) when:

  • Your integration is complete and tested
  • You're ready to create real bookings for customers
  • You're running in a live/production environment

Important Notes:

  • Both environments use the same authentication method
  • Each environment has its own API credentials (staging and production keys are separate)
  • Data created in staging is separate from production
  • Always test thoroughly in staging before using production

Authentication

Hubby API uses HMAC-SHA256 authentication. Every request must include three headers:

Required Headers

  1. x-api-key: Your public API key
  2. x-timestamp: Current timestamp in milliseconds (Unix epoch time)
  3. x-signature: HMAC-SHA256 signature of the request

How Authentication Works

The signature is computed by creating an HMAC-SHA256 hash of a message string using your secret key. The message format is:

{timestamp}{HTTP_METHOD}/api{path}

For example, for a POST request to /api/bookings:

  • If timestamp is 1704067200000
  • The message would be: 1704067200000POST/api/bookings

Authentication Implementation

Here's how to generate the authentication headers:

const crypto = require('crypto');

function generateAuthHeaders(method, path, apiKey, apiSecret) {
  // Get current timestamp in milliseconds
  const timestamp = Date.now().toString();
  
  // Create the message to sign
  const message = `${timestamp}${method}/api${path}`;
  
  // Generate HMAC-SHA256 signature
  const signature = crypto
    .createHmac('sha256', apiSecret)
    .update(message)
    .digest('hex');
  
  // Return headers
  return {
    'x-api-key': apiKey,
    'x-timestamp': timestamp,
    'x-signature': signature,
    'Content-Type': 'application/json'
  };
}

Important Authentication Notes

  • Timestamp must be current: The timestamp must be within 5 minutes of the server time
  • Use exact path: The path in the signature must include /api (e.g., /api/bookings). The route /bookings does not exist on its own
  • Method must be uppercase: Use GET, POST, PUT, DELETE (uppercase)
  • Keep your secret secure: Never expose your API secret key in client-side code or public repositories

Creating a Booking

Endpoint

Production:

POST https://api.hubbyesim.com/api/bookings

Staging (for testing):

POST https://api-staging.hubby.dev/api/bookings

Request Body

For a starter package booking with a destination, you need:

{
  "booking_id": "YOUR-BOOKING-123",
  "departure_date": "2025-06-15T00:00:00.000Z",
  "return_date": "2025-06-22T00:00:00.000Z",
  "departure_location": "DMK", 
  "first_name": "John",
  "communication_options": {
    "should_send_message": true,
    "channels": ["EMAIL"]
  },
  "package_specifications": [
    {
      "package_type": "starter",
      "destination": "Thailand"
    }
  ]
}

Field Descriptions

  • booking_id (required if email is not provided): Any unique reference you want to store with us for this booking. This is not the customer-facing booking ID, but rather an internal identifier you can use to correlate bookings in your system with Hubby records. Either booking_id or email must be provided
  • departure_date (required): Travel start date in ISO 8601 format (e.g., "2025-06-15T00:00:00.000Z")
  • return_date (optional): Travel end date in ISO 8601 format (e.g., "2025-06-22T00:00:00.000Z")
  • departure_location (optional): The customer's departure airport code (IATA 3-letter code, e.g., "AMS", "JFK", "LHR"). Useful for analytics and localized communications
  • email (required if booking_id is not provided): Customer email address. If provided, Hubby sends eSIM installation instructions directly to the customer. Either email or booking_id must be provided; both can be supplied
  • first_name (optional): Customer first name
  • last_name (optional): Customer last name
  • communication_options (optional): Controls whether Hubby sends emails to the customer. If not provided, messages will not be sent
    • should_send_message (boolean): Set to true to allow Hubby to send emails, or false to handle communication yourself
    • channels (array): List of communication channels to use. Available options: "EMAIL", "SMS", "WHATSAPP", "PUSH"
  • package_specifications (required): Array with at least one package specification. Each entry is resolved by package_id (direct reference) or by other properties (destination, size, etc.); when package_id is set, other fields in that entry are ignored.
  • package_type: "starter": Creates a starter package
  • destination (required): Country name or ISO 3166-1 alpha-2 country code (e.g., "Thailand" or "TH")

Complete Example

Here's a complete example using Node.js:

const crypto = require('crypto');
const https = require('https');

// Your API credentials (store these securely!)
const API_KEY = 'your-api-key-here';
const API_SECRET = 'your-api-secret-here';
// 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';

function generateAuthHeaders(method, path, apiKey, apiSecret) {
  const timestamp = Date.now().toString();
  const message = `${timestamp}${method}/api${path}`;
  const signature = crypto
    .createHmac('sha256', apiSecret)
    .update(message)
    .digest('hex');
  
  return {
    'x-api-key': apiKey,
    'x-timestamp': timestamp,
    'x-signature': signature,
    'Content-Type': 'application/json'
  };
}

async function createBooking(bookingData) {
  const path = '/bookings';
  const url = `${API_BASE_URL}/api${path}`;
  
  const headers = generateAuthHeaders('POST', path, API_KEY, API_SECRET);
  
  const requestBody = {
    booking_id: bookingData.bookingId,
    departure_date: bookingData.departureDate, // ISO 8601 format
    return_date: bookingData.returnDate,       // ISO 8601 format
    departure_location: bookingData.departureLocation,
    email: bookingData.email,
    first_name: bookingData.firstName,
    last_name: bookingData.lastName,
    communication_options: {
      should_send_message: true,  // Set to false if you handle emails yourself
      channels: ['EMAIL']
    },
    package_specifications: [
      {
        package_type: 'starter',
        destination: bookingData.destination
      }
    ]
  };
  
  return new Promise((resolve, reject) => {
    const urlObj = new URL(url);
    const options = {
      hostname: urlObj.hostname,
      port: urlObj.port || 443,
      path: urlObj.pathname,
      method: 'POST',
      headers: headers
    };
    
    const req = https.request(options, (res) => {
      let data = '';
      
      res.on('data', (chunk) => {
        data += chunk;
      });
      
      res.on('end', () => {
        try {
          const response = JSON.parse(data);
          if (res.statusCode === 200 || res.statusCode === 201) {
            resolve(response);
          } else {
            reject(new Error(`API Error: ${res.statusCode} - ${data}`));
          }
        } catch (e) {
          reject(new Error(`Failed to parse response: ${e.message}`));
        }
      });
    });
    
    req.on('error', (error) => {
      reject(error);
    });
    
    req.write(JSON.stringify(requestBody));
    req.end();
  });
}

// Example usage
async function main() {
  try {
    const result = await createBooking({
      bookingId: 'BOOKING-12345',
      departureDate: '2025-06-15T00:00:00.000Z',
      returnDate: '2025-06-22T00:00:00.000Z',
      departureLocation: 'DMK',
      email: 'customer@example.com',
      firstName: 'John',
      lastName: 'Doe',
      destination: 'Thailand'
    });
    
    console.log('Booking created successfully!');
    console.log('Booking ID:', result.data.id);
    console.log('Promo Code:', result.data.promo_codes[0].promo_code);
    console.log('Claim URL:', result.data.promo_codes[0].claim_url);
    console.log('Destination:', result.data.promo_codes[0].destination);
  } catch (error) {
    console.error('Error creating booking:', error.message);
  }
}

main();

Using Fetch (Modern JavaScript)

const crypto = require('crypto');

const API_KEY = 'your-api-key-here';
const API_SECRET = 'your-api-secret-here';
// 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';

function generateAuthHeaders(method, path, apiKey, apiSecret) {
  const timestamp = Date.now().toString();
  const message = `${timestamp}${method}/api${path}`;
  const signature = crypto
    .createHmac('sha256', apiSecret)
    .update(message)
    .digest('hex');
  
  return {
    'x-api-key': apiKey,
    'x-timestamp': timestamp,
    'x-signature': signature,
    'Content-Type': 'application/json'
  };
}

async function createBooking(bookingData) {
  const path = '/bookings';
  const url = `${API_BASE_URL}/api${path}`;
  const headers = generateAuthHeaders('POST', path, API_KEY, API_SECRET);
  
  const requestBody = {
    booking_id: bookingData.bookingId,
    departure_date: bookingData.departureDate, // ISO 8601 format
    return_date: bookingData.returnDate,       // ISO 8601 format
    departure_location: bookingData.departureLocation,
    email: bookingData.email,
    first_name: bookingData.firstName,
    last_name: bookingData.lastName,
    communication_options: {
      should_send_message: true,  // Set to false if you handle emails yourself
      channels: ['EMAIL']
    },
    package_specifications: [
      {
        package_type: 'starter',
        destination: bookingData.destination
      }
    ]
  };
  
  const response = await fetch(url, {
    method: 'POST',
    headers: headers,
    body: JSON.stringify(requestBody)
  });
  
  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`API Error: ${response.status} - ${errorText}`);
  }
  
  return await response.json();
}

// Example usage
createBooking({
  bookingId: 'BOOKING-12345',
  departureDate: '2025-06-15T00:00:00.000Z',
  returnDate: '2025-06-22T00:00:00.000Z',
  departureLocation: 'DMK',
  email: 'customer@example.com',
  firstName: 'John',
  lastName: 'Doe',
  destination: 'Thailand'
})
  .then(result => {
    console.log('Booking created!');
    console.log('Promo Code:', result.data.promo_codes[0].promo_code);
    console.log('Claim URL:', result.data.promo_codes[0].claim_url);
    console.log('Destination:', result.data.promo_codes[0].destination);
  })
  .catch(error => {
    console.error('Error:', error.message);
  });

Response

A successful booking creation returns:

{
  "success": true,
  "message": "Booking created successfully.",
  "data": {
    "id": "xs8gWi1GXHWa7YyV4HVG",
    "booking_id": "YOUR-BOOKING-123",
    "first_name": "John",
    "last_name": "Doe",
    "departure_date": "2025-06-15T00:00:00.000Z",
    "return_date": "2025-06-22T00:00:00.000Z",
    "promo_codes": [
      {
        "promo_code": "CXZ6ZM9NGA",
        "uuid": "5163cbc9-a96a-4a97-8577-bd3416bd3e4b",
        "claim_url": "https://platform.hubbyesim.com/claim-promocode/acme?uuid=51631111-1111-1aa1-1ee6-bd3416bd3e4b",
        "package_type": "starter",
        "package_size": "1GB",
        "package_duration": 365,
        "destination": "TH"
      }
    ]
  }
}

Using the Promo Code

You have two options for providing the promo code to your customer:

When you include an email address in the booking, Hubby automatically sends the promo code with installation instructions to the customer. You can also provide the promo code directly:

  1. Extract the promo_code from the response: result.data.promo_codes[0].promo_code
  2. Provide this code to your customer (via email, SMS, or in your booking confirmation)
  3. The customer enters the code in the Hubby app or on the Hubby website
  4. The destination is pre-selected based on the booking

Example:

const promoCode = result.data.promo_codes[0].promo_code;
// Store or display: "Your eSIM promo code: [promo_code]"

Option 2: Using the Claim URL

If you are not providing an email address in the booking, you can use the claim_url to collect the customer's email and deliver the promo code:

  1. Extract the claim_url from the response: result.data.promo_codes[0].claim_url
  2. Include this URL in your booking confirmation email or SMS
  3. When customers click the link, they're taken to a page where they enter their email
  4. Hubby then sends them the promo code with installation instructions

Example:

const claimUrl = result.data.promo_codes[0].claim_url;
// Include in confirmation: "Click here to get your eSIM: [claim_url]"

Error Handling

Common error responses:

  • 401 Unauthorized: Invalid or missing authentication headers
  • 400 Bad Request: Invalid request data (missing required fields, invalid date format, invalid country code, etc.)
  • 500 Internal Server Error: Server-side error

Always check the response status and handle errors appropriately:

if (!response.ok) {
  const error = await response.json();
  console.error('API Error:', error);
  // Handle error appropriately
}

Best Practices

  1. Store credentials securely: Never hardcode API keys in your source code. Use environment variables or secure configuration management
  2. Handle errors gracefully: Implement proper error handling and retry logic for network issues
  3. Validate input: Validate customer data and country codes before sending to the API
  4. Include email addresses: Provide customer email addresses when possible so Hubby can send installation instructions automatically
  5. Store promo codes: Save the promo codes in your database for customer support purposes
  6. Test in development: Use test credentials and test bookings before going to production
  7. Use valid country codes: Ensure you're using valid ISO 3166-1 alpha-2 country codes for destinations

Next Steps

  1. Set up your API credentials
  2. Learn about other package types
  3. Implement error handling

Support

For integration support or questions:

  • Email: tech@hubbyesim.com
  • Check the API reference for complete endpoint documentation