eSimfly Logo
TUTORIAL NODE.JS · ~30 MINUTOS

Cómo vender eSIM en tu app con Node.js

Monta una tienda de eSIM funcional sobre la API de eSIMfly con el SDK oficial @esimfly/sdk: autentícate, replica el catálogo de paquetes a precio mayorista, crea pedidos con cargo al saldo de tu cuenta y entrega a tu usuario un código QR listo para escanear, sin escribir ni una línea de firma de peticiones.

Requisitos previos

  • Una cuenta business de eSIMfly con saldo: solicita acceso a la API aquí
  • Credenciales de la API (código de acceso + clave secreta) desde Business Dashboard → Settings → API Keys
  • Node.js 18+ y el SDK: npm install @esimfly/sdk (sin dependencias, con tipos TypeScript incluidos)

1. Instala el SDK y autentícate

Cada petición necesita cuatro cabeceras: tu código de acceso, un ID de petición único, una marca de tiempo y una firma HMAC-SHA256 calculada con tu clave secreta. El SDK las genera en cada llamada, así que la autenticación se reduce a un constructor. Mantén el cliente en tu servidor: la clave secreta nunca debe llegar a un navegador ni a una app móvil.

// $ npm install @esimfly/sdk

// esimfly.js - one client for your whole backend (server-side only!)
import { ESIMfly } from '@esimfly/sdk';

export const esimfly = new ESIMfly({
  accessCode: process.env.ESIMFLY_ACCESS_CODE, // esf_...
  secretKey: process.env.ESIMFLY_SECRET_KEY,   // sk_...
});

// Every call is signed for you: a fresh request ID (UUID v4), a millisecond
// timestamp and an HMAC-SHA256 signature of
//   timestamp + requestId + accessCode + requestBody
// computed with your secret key. Requests older than 5 minutes are rejected.
const { balance, currency } = await esimfly.balance.get();
console.log(balance, currency); // 250.00 'USD'

2. Consulta el catálogo de paquetes

El catálogo devuelve todos los paquetes que puedes vender con tu precio mayorista; la cobertura abarca 200 países y más de 450 redes. Consúltalo de forma puntual mientras desarrollas, pero en producción replícalo en tu propia base de datos de forma programada y sirve tu tienda desde ahí: los precios cambian cuando los operadores modifican sus tarifas y el endpoint de pedidos siempre cobra el precio vigente.

// Ad-hoc lookup: packages for one destination, with YOUR wholesale price
const { packages } = await esimfly.packages.list({ search: 'Turkey', type: 'local', limit: 50 });
console.log(packages[0]);
// { package_code: '1648812', name: 'Turkey 1 GB 7 Days', type: 'local',
//   data_amount_gb: 1, validity_days: 7, cost: 2.72, currency: 'USD', ... }

// Production: mirror the whole catalog into your database on a schedule
// (every 6-12 h) and serve your storefront from it. listAll()/sync() page
// with limit=100 and pause 1 s between pages, so a full sync stays inside
// the rate limit.
await esimfly.packages.sync(async (batch) => {
  await db.packages.upsertMany(batch.map((p) => ({
    packageCode: p.package_code,   // opaque - store it verbatim
    name: p.name,
    cost: p.cost,                  // your buy price
    sellPrice: p.cost * 1.3,       // your margin, your rules
    dataGb: p.data_amount_gb,
    validityDays: p.validity_days,
  })));
});

3. Crea un pedido

Una sola llamada con el código del paquete compra la eSIM con cargo al saldo de tu cuenta y devuelve todo lo necesario para instalarla: cadena LPA, imagen del código QR y enlaces de instalación con un toque. Pasa siempre una idempotencyKey (tu propio ID de pedido): si la petición expira, reintentar con la misma clave devuelve el pedido original en lugar de cobrarte dos veces. La mayoría de los pedidos se completan al instante; unos pocos paquetes devuelven status: "pending_details" y waitForEsim() sondea hasta que están listos.

const order = await esimfly.orders.create({
  packageCode: '1648812',
  quantity: 1,
  idempotencyKey: cart.id, // your own id: a retry can never charge you twice
});

console.log(order.orderReference); // order_1692123456_ab7cd
console.log(order.newBalance);     // 47.28 - charged from your balance

const esim = order.esims[0];
console.log(esim.iccid);           // 8932042000010078801
console.log(esim.lpaString);       // LPA:1$rsp-3104.idemia.io$DOAZJ-...

// A few packages are provisioned asynchronously (status: 'pending_details').
// waitForEsim() polls the order every 20 s for up to 10 minutes:
if (order.status === 'pending_details') {
  const ready = await esimfly.orders.waitForEsim(order.orderReference);
  console.log(ready.esim.iccid);
}

4. Entrega la eSIM a tu usuario

Cada pedido incluye tres mecanismos de entrega, y conviene mostrarlos todos: el código QR para instalar desde otro dispositivo, el enlace universal de Apple para instalar en iOS con un toque y la cadena LPA en bruto como alternativa manual.

// Everything your user needs is in order.esims[i]:
//
// esim.lpaString               -> render your own QR code from it (any QR library)
//                                 and show it for manual entry
//                                 (Settings > Cellular > Add eSIM > Enter manually)
// esim.directAppleInstallUrl   -> one-tap install on iOS 17.4+ (universal link)
// esim.directAndroidInstallUrl -> one-tap install on supported Android devices
// esim.qrCodeUrl               -> ready-made base64 PNG if you prefer not to render

await db.esims.create({
  orderReference: order.orderReference,
  iccid: esim.iccid,
  lpaString: esim.lpaString,
  appleUrl: esim.directAppleInstallUrl,
  androidUrl: esim.directAndroidInstallUrl,
  expiresAt: esim.expired_time,
});

app.get('/my-esim/:orderRef', async (req, res) => {
  const esim = await db.esims.findByOrder(req.params.orderRef);
  res.render('esim', { lpa: esim.lpaString, appleUrl: esim.appleUrl, androidUrl: esim.androidUrl });
});

5. Pasa a producción

Todo fallo es un ESIMflyError con el code de la API: decide según el código, nunca según el mensaje:

import { ESIMflyError } from '@esimfly/sdk';

try {
  await esimfly.orders.create({ packageCode, idempotencyKey: cart.id });
} catch (err) {
  if (err instanceof ESIMflyError) {
    switch (err.code) {
      case 'INSUFFICIENT_BALANCE': // err.response.needToLoad tells you how much
        return alertOps(err.response);
      case 'INVALID_PACKAGE':      // catalog changed - resync
        return resyncCatalog();
      case 'RATE_LIMIT_EXCEEDED':
        return retryLater();
    }
  }
  throw err;
}
  • Guarda la respuesta del pedido: conserva orderReference, el ICCID y los datos de instalación para que los usuarios puedan volver a abrir su eSIM más tarde.
  • Gestiona los errores de saldo: los pedidos fallan de forma controlada si el saldo es insuficiente; vigila newBalance y avísate antes de que se agote.
  • Respeta los límites: son por clave de API (normalmente 100 peticiones por minuto, 1000 por hora y 10 000 al día); la sincronización del catálogo con ritmo controlado y las consultas de consumo en caché del SDK mantienen una tienda normal muy por debajo. Sincroniza los relojes de tus servidores con NTP: las firmas con más de 5 minutos se rechazan.
  • Suscríbete a los webhooks: entérate al instante de cuándo se instala una eSIM, se activa o se queda sin datos, en lugar de sondear. Continúa con Recargas, control de consumo y webhooks.

¿Listo para empezar a construir?

La referencia completa de endpoints, ejemplos en Python y PHP y un entorno de pruebas interactivo están en la documentación.