Last updated

Claim Links

This guide demonstrates how to integrate the Hubby eSIM API to create bookings with destinationless starter packages, where a claim link is generated for every promo code created on the booking.

This is perfect for simple integrations where customers can choose their destination later, and you can simply send them a link that takes them straight to redemption.

Overview

In this integration:

  1. You create a booking with a destinationless starter package
  2. The system creates one or more promo codes for that booking
  3. For each promo code, the system also generates a claim_url
  4. You send the claim link(s) to your customer
  5. The customer clicks the link, redeems the eSIM, and selects a destination during redemption

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
  • Your API credentials work in both environments
  • 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 match the actual API path (e.g., /bookings not /api/bookings in the message)
  • 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

Endpoint

Production:

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

Staging (for testing):

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

Request Body

For a destinationless starter package booking, you need:

{
  "booking_id": "YOUR-BOOKING-123",
  "departure_date": "2025-06-15",
  "first_name": "John",
  "last_name": "Doe",
  "package_specifications": [
    {
      "package_type": "starter",
      "size": "500MB",
      "package_duration": 2
    }
  ]
}

Field Descriptions

  • booking_id (optional but recommended): Your internal booking reference
  • departure_date (required): Travel date in YYYY-MM-DD format
  • first_name (required): Customer first name
  • last_name (required): Customer last name
  • 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 destinationless starter package (customer selects destination later)
  • size (required for all types except unlimited): Data allowance for the package
  • package_duration (required for starter, time-limited, and unlimited): Duration in days

Complete Example (Node.js)

This example creates a booking and then prints all promo codes and their claim URLs.

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,
    email: bookingData.email ?? null,
    first_name: bookingData.firstName,
    last_name: bookingData.lastName,
    package_specifications: bookingData.packageSpecifications ?? [
      { package_type: 'starter', size: '500MB', package_duration: 2 }
    ]
  };

  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
    };

    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', reject);
    req.write(JSON.stringify(requestBody));
    req.end();
  });
}

// Example usage
async function main() {
  try {
    const result = await createBooking({
      bookingId: 'YOUR-BOOKING-123',
      departureDate: '2025-06-15',
      firstName: 'John',
      lastName: 'Doe',
      // Optional: specify multiple packages (=> multiple promo codes => multiple claim links)
      // packageSpecifications: [
      //   { package_type: 'starter', size: '500MB', package_duration: 2 },
      //   { package_type: 'starter', size: '500MB', package_duration: 2 }
      // ]
    });

    console.log('Booking created successfully!');
    console.log('Booking ID:', result.data.id);

    // Loop all promo codes and their links
    for (const pc of result.data.promo_codes ?? []) {
      console.log('---');
      console.log('Promo Code:', pc.promo_code);
      console.log('UUID:', pc.uuid);
      console.log('Claim URL:', pc.claim_url);
      console.log('Deep Link URL:', pc.deep_link_url);
    }
  } catch (error) {
    console.error('Error creating booking:', error.message);
  }
}

main();

Response

A successful booking creation returns the booking plus an array of promo codes. Each promo code includes a claim_url.

{
  "success": true,
  "message": "Booking created successfully.",
  "data": {
    "id": "RKI8w2e09kImpBnJ2iHH",
    "title": null,
    "first_name": "John",
    "last_name": "Doe",
    "full_name": "John",
    "pax": 1,
    "email": null,
    "phone": null,
    "booking_id": "YOUR-BOOKING-123",
    "external_id": "agentnumber1",
    "return_date": "2025-06-15T00:00:00.000Z",
    "departure_date": "2025-06-15T00:00:00.000Z",
    "gender": "M",
    "locale": "de-DE",
    "created_at": "2026-01-12T14:55:04.724Z",
    "promo_codes": [
      {
        "promo_code": "WSTZXS9HK7",
        "uuid": "f18f9757-ffff-ffff-ffff-6b2c8af66b2c",
        "claim_url": "https://platform.hubbyesim.com/claim-promocode/bronze?uuid=f18f9757-1365-426d-bd32-6b2c8af66b2c",
        "deep_link_url": "https://hubby-esim.web.app/starter/?promoCode=WSTZXS9HK7",
        "package_id": "",
        "package_size": "",
        "destination": "Türkei",
        "iso3": "TUR",
        "package_type": "starter",
        "package_duration": 2
      }
    ],
    "updated_at": "2026-01-12T14:55:05.302Z",
    "status": "COMPLETED"
  }
}

You generally don’t need to display the raw promo code anymore. Instead, you can send the claim link or deep link to the traveler.

Each promo code in the response includes two links:

  • claim_url: A web link where the user enters their email to receive the promo code
  • deep_link_url: A mobile deep link that opens the Hubby app and auto-applies the promo code

How it works

  1. Extract the links from the response:
    • result.data.promo_codes[i].claim_url
    • result.data.promo_codes[i].deep_link_url
  2. Include the link(s) in your booking confirmation email, SMS, or itinerary
  3. The traveler clicks the link and redeems the eSIM on the Hubby platform
  4. The traveler continues the flow (including destination choice for starter packages)

Example (single promo code):

const claimUrl = result.data.promo_codes[0].claim_url;
const deepLinkUrl = result.data.promo_codes[0].deep_link_url;
// Include in email: "Activate your eSIM: <claimUrl>"
// Or for mobile: "Open in app: <deepLinkUrl>"

Example (multiple promo codes):

const links = (result.data.promo_codes ?? []).map(p => ({
  claim: p.claim_url,
  deepLink: p.deep_link_url,
}));
// Render these in your UI/email as separate "Activate eSIM" buttons/links.
  • Less friction: no copy/paste code needed
  • Easier support: you can store the UUID/claim URL and look it up later
  • Better follow-up options: claim flow allows capturing traveler contact details (when applicable)
  • Mobile-optimized: deep links open the Hubby app directly for one-tap redemption

Option: Still show the Promo Code

If you want a fallback, you can still show:

  • promo_code for manual entry
  • uuid for internal support and reconciliation

But for the best experience, send the claim link.

Error Handling

Common error responses:

  • 401 Unauthorized: Invalid or missing authentication headers
  • 400 Bad Request: Invalid request data (missing required fields, invalid date format, 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: Use environment variables or a secrets manager
  2. Store claim URLs and UUIDs: Save them for support and re-sending to the customer
  3. Send claim URLs by default: It’s the simplest flow for travelers
  4. Handle multiple promo codes: Loop over promo_codes[] and send one claim link per promo code
  5. Test in staging first: Validate signing, payloads, and parsing logic before production

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