Selling the eSIM is half the product — the other half is what happens after: showing customers their remaining data, selling top-ups at the right moment, and handling the one provider (KDDI Japan) that delivers eSIMs asynchronously via webhook - everything else is served by simple polled queries. This guide covers all three.
New here? Start with part one: Sell eSIMs in Your App with Node.js — it builds the hmacHeaders() helper used below.
Query any eSIM by ICCID for real-time consumption. The interesting business logic lives on top of this: notify at 80% usage, surface remaining data in your app, or trigger an automatic top-up offer before the customer runs out mid-trip.
// hmacHeaders() from part one of this tutorial
async function getUsage(iccid) {
const query = '?iccid=' + encodeURIComponent(iccid);
const res = await fetch(
BASE_URL + '/api/v1/business/esims/usage/query' + query,
{ headers: hmacHeaders() }
);
return res.json();
}
const usage = await getUsage('8910300001234567890');
console.log(usage.data.data);
// { total_mb: 1024, used_mb: 256, remaining_mb: 768,
// usage_percentage: 25, is_unlimited: false }
// Perfect for "80% used - top up now?" push notificationsTop-ups are the highest-margin repeat sale in the eSIM business — the customer already has the profile installed, so there's zero onboarding friction. Always fetch compatible packages per ICCID first: not every package can top up every eSIM.
// 1. Which packages can THIS eSIM be topped up with?
async function getTopupPackages(iccid) {
const query = '?iccid=' + encodeURIComponent(iccid);
const res = await fetch(
BASE_URL + '/api/v1/business/topup/packages' + query,
{ headers: hmacHeaders() }
);
const data = await res.json();
return data.data.packages;
}
// 2. Apply a top-up - charged from your balance, active immediately
async function topUp(iccid, packageCode) {
const body = JSON.stringify({ iccid, packageCode });
const res = await fetch(BASE_URL + '/api/v1/business/topup/order', {
method: 'POST',
headers: hmacHeaders(body),
body
});
return res.json();
}Some providers (like KDDI Japan) provision asynchronously: your order returns status: "pending_details" and the eSIM details arrive by webhook 1–5 minutes later. Register a global webhook URL once, or pass callbackUrl per order.
// One-time setup: register your webhook endpoint
const body = JSON.stringify({
webhook_url: 'https://your-server.com/api/esim-callback',
events: ['esim.provisioned']
});
const res = await fetch(BASE_URL + '/api/v1/business/webhooks', {
method: 'PUT',
headers: hmacHeaders(body),
body
});
const { webhook } = await res.json();
// SAVE webhook.secret (whsec_...) - you need it to verify signatures.
// Alternative: pass callbackUrl per-order in the order request instead.Every delivery is signed with HMAC-SHA256 (header X-Webhook-Signature). Verify against the raw request body with a timing-safe comparison — never process an unsigned payload, and never verify against re-serialized JSON.
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signatureHeader, webhookSecret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', webhookSecret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}
// Express: use the RAW body for verification, not the parsed JSON
app.post('/api/esim-callback',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-webhook-signature'];
const rawBody = req.body.toString();
if (!verifyWebhookSignature(rawBody, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(rawBody);
if (event.event === 'esim.provisioned') {
const { order_reference, iccid, qr_code_url, lpa_string } = event.data;
// The eSIM is ready - deliver it to your customer now
}
res.status(200).send('ok'); // always ack fast, process async
});X-Webhook-Id header so retries never double-deliver an eSIM.Suspend, cancel, SMS delivery, network events and more — the complete endpoint reference is in the documentation.