New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@esimfly/sdk

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@esimfly/sdk

Official Node.js / TypeScript SDK for the eSIMfly Business API — sell eSIMs, manage usage and receive webhooks

latest
Source
npmnpm
Version
0.1.0
Version published
Maintainers
1
Created
Source

@esimfly/sdk — Node.js / TypeScript SDK for the eSIMfly Business API

Sell eSIMs from your own app: browse the catalogue, order, top up, track usage and receive signed webhooks — with request signing, retries and error handling done for you.

  • Zero runtime dependencies, Node.js 18+ (uses the built-in fetch)
  • TypeScript types for every request and response, ESM + CommonJS
  • HMAC-SHA256 signing with a fresh request id per call
  • Idempotency keys on orders, paced catalogue sync, pending-order polling
  • verifyWebhookSignature / constructWebhookEvent for deliveries

Full API reference: https://docs.esimfly.net · Get credentials: Business Dashboard → Settings → API Keys.

Keep the SDK on your server. The secret key must never be shipped to a browser or mobile app.

Install

npm install @esimfly/sdk

Quick start

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

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

const { balance, currency } = await esimfly.balance.get();
console.log(`Balance: ${balance} ${currency}`);

Sell an eSIM in three steps

1. Sync the catalogue into your database (scheduled, every 6–12 h)

Do not call the catalogue per customer request — copy it and serve your storefront from your own tables. listAll pages with limit=100 and pauses 1 s between pages to stay inside the rate limit.

const runStartedAt = new Date();

await esimfly.packages.sync(async (packages) => {
  await db.packages.upsertMany(
    packages.map((p) => ({
      packageCode: p.package_code,     // opaque — store verbatim
      name: p.name,
      region: p.region,
      type: p.type,                    // local | regional | global
      dataGb: p.data_amount_gb,
      validityDays: p.validity_days,
      cost: p.cost,                    // your buy price
      currency: p.currency,
      sellPrice: p.cost * 1.3,         // your margin, your rules
      countries: p.countries ?? [],
      lastSeenAt: runStartedAt,
      isActive: true,
    })),
  );
});

// Only after a fully successful run: hide packages that disappeared (never delete them —
// your orders reference them).
await db.packages.updateMany({ where: { lastSeenAt: { lt: runStartedAt } }, data: { isActive: false } });

2. Create the order (inside your checkout)

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

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

  await db.orders.update(cart.id, {
    orderReference: order.orderReference,
    amount: order.amount,
    currency: order.currency,
    status: order.status,              // 'completed' | 'pending_details'
  });

  for (const esim of order.esims) {
    await db.esims.create({
      iccid: esim.iccid,
      lpaString: esim.lpaString,        // render your own QR code from this
      appleInstallUrl: esim.directAppleInstallUrl,
      androidInstallUrl: esim.directAndroidInstallUrl,
      expiresAt: esim.expired_time,
      totalBytes: esim.total_volume,
    });
  }

  if (order.status === 'pending_details') {
    // Rare (asynchronously provisioned packages). Poll up to 10 minutes, then hand to support.
    const ready = await esimfly.orders.waitForEsim(order.orderReference);
    console.log('eSIM ready:', ready.esim.iccid);
  }
} catch (err) {
  if (err instanceof ESIMflyError && err.code === 'INSUFFICIENT_BALANCE') {
    const { needToLoad } = err.response as { needToLoad: number };
    alertOps(`Top up the eSIMfly balance: ${needToLoad}`);
  } else {
    throw err;
  }
}

3. Deliver and support

// "My eSIM" screen — cheap, cache 5–15 min
const usage = await esimfly.esims.usage({ iccid });
console.log(`${usage.data.remaining_mb} MB left, expires ${usage.validity.expires_at}`);

// Top-up screen
const { packages } = await esimfly.topups.packages({ iccid, limit: 100 });
await esimfly.topups.create({ iccid, packageCode: packages[0].package_code });

// Support console — live from the network, throttle per eSIM
const live = await esimfly.esims.status({ iccid });
console.log(live.last_network.operator, live.device.model, live.data_usage.used_gb);
const events = await esimfly.esims.networkEvents({ iccid }); // events[].is_allowed === false → wrong network

// Actions
await esimfly.esims.suspend({ iccid });   // eSIMfly-network eSIMs only
await esimfly.esims.activate({ iccid });
await esimfly.esims.sendSms({ iccid }, 'Your eSIM is ready. Enable Data Roaming to connect.');
await esimfly.esims.cancel({ iccid });    // only before installation — refunds to your balance

Webhooks

Subscribe once, then react to events instead of polling.

const { webhook } = await esimfly.webhooks.set({
  webhookUrl: 'https://your-server.com/api/esimfly-webhook',
  events: ['esim.installed', 'esim.status.changed', 'esim.usage.threshold'],
});
await secrets.save('ESIMFLY_WEBHOOK_SECRET', webhook.secret); // shown once

Receiver (Express) — verify on the raw body, dedupe on X-Webhook-Id, answer within 10 s:

import express from 'express';
import { constructWebhookEvent, ESIMflyError } from '@esimfly/sdk';

app.post('/api/esimfly-webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  let event;
  try {
    event = constructWebhookEvent(req.body, req.header('X-Webhook-Signature'), process.env.ESIMFLY_WEBHOOK_SECRET!);
  } catch (err) {
    return res.status(err instanceof ESIMflyError && err.code === 'INVALID_SIGNATURE' ? 401 : 400).end();
  }

  const deliveryId = req.header('X-Webhook-Id')!;
  if (await db.webhookDeliveries.exists(deliveryId)) return res.json({ received: true });
  await db.webhookDeliveries.insert({ id: deliveryId, event: event.event, payload: event.data });
  res.json({ received: true });

  // process after responding
  switch (event.event) {
    case 'esim.installed':
      await db.esims.update({ iccid: event.data.iccid }, { installedAt: event.data.installed_at });
      break;
    case 'esim.status.changed':
      await db.esims.update({ iccid: event.data.iccid }, { status: event.data.new_status, expiresAt: event.data.expiry_date });
      break;
    case 'esim.usage.threshold':
      if ((event.data.threshold_remaining_mb ?? Infinity) <= 200) await notifyLowData(event.data.iccid);
      break;
  }
});
EventWhenLatency
esim.installedprofile enabled on a device for the first timeseconds
esim.profile.updatedevery SM-DP+ state change (chatty — usually skip)seconds
esim.usage.threshold500 / 200 / 100 / 50 MB remainingseconds
esim.status.changedNEW → ACTIVE → DEPLETED / EXPIRED / CANCELLED≤ 30 min
esim.provisionedasynchronously provisioned order readyseconds

Errors

Every failure is an ESIMflyError. Branch on code, never on the message.

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

try {
  await esimfly.topups.create({ iccid, packageCode });
} catch (err) {
  if (err instanceof ESIMflyError) {
    err.code;        // 'ESIM_NOT_TOPPABLE' | 'INSUFFICIENT_BALANCE' | 'RATE_LIMIT_EXCEEDED' | ...
    err.status;      // HTTP status
    err.response;    // parsed API body (extra fields such as needToLoad, details.ineligibleEsims)
    err.requestId;   // the RT-RequestID that was sent — quote it to support
    err.isRetryable; // network / timeout / 5xx
  }
}

Retries: GETs and orders that carry an idempotencyKey are retried up to maxRetries (default 2) on network errors, timeouts and 5xx, always with a fresh request id. Top-ups and orders without a key are never retried automatically — on a timeout, check esims.usage({ iccid }) before retrying.

Rate limits are per API key (typically 100/min, 1,000/h, 10,000/day). The last response's headers are on esimfly.rateLimit (limit, remaining, reset); a rejection surfaces as RATE_LIMIT_EXCEEDED.

Configuration

new ESIMfly({
  accessCode: 'esf_...',
  secretKey: 'sk_...',
  baseUrl: 'https://esimfly.net/api/v1/business', // default
  timeoutMs: 30_000,                                // default
  maxRetries: 2,                                    // default; 0 disables
  fetch: customFetch,                               // proxies, tests
  userAgent: 'my-shop/2.1',                         // appended to the SDK user agent
});

API surface

ResourceMethods
balanceget()
packageslist(params), listAll(options) (async iterator), sync(handler, options)
orderscreate(params), get(orderReference), waitForEsim(orderReference, options), list(params)
esimslist(params), find(iccid), usage({ iccid } | { orderId }), status(id), networkEvents(id), usageReport(id, days), suspend(id), activate(id), cancel(id), sendSms(id, message)
topupspackages({ iccid }), create({ iccid, packageCode })
webhooksget(), set({ webhookUrl, events })
helpersverifyWebhookSignature(rawBody, header, secret), constructWebhookEvent(rawBody, header, secret), signRequest(...)

id is { iccid } or { esimId }.

Support

support@esimfly.net · https://docs.esimfly.net

Keywords

esim

FAQs

Package last updated on 13 Sep 2026

Related posts