externalReference, the plan slug, the paying account, the customer it is for, and the state you last saw — and treat everything after the order as something you resume rather than something you complete in one request.
1. Mirror the catalog
Two reads, top down, both paged and read-only.slug against your product record. Two fields decide what you can offer later, so keep them:
Re-read the plan before you quote if your copy is older than an hour. A plan withdrawn from the network between your mirror and your order is
404 esim_package_unavailable, and the customer is already at a confirmation screen by then.
2. Quote, and show the figure you will charge
The catalog is USD. Your account may not be. Quote unless the paying account is USD, and putpayable on the confirmation screen — never the USD price.
409 esim_fx_quote_required and they all end the same way.
3. Create the order
provisioning with an empty profileIds and design for it — a “preparing” state the customer can walk away from, not a blocking spinner.
Pass customerId when you know who the eSIM is for. It does not change who is billed — your account is debited either way — but it is what lets you list a customer’s eSIMs later, and what a support agent will search on.
4. Wait for fulfilment
Take the webhook, and keep polling as the recovery path rather than the design:esim.order.completedcarries the order,profileIdsincluded. That is your signal to fetch install details.esim.order.failedmeans the network refused. The money is on its way back; you have nothing to claim.- Polling the order every few seconds while
statusisprovisioningis the fallback when a webhook is missed.
provisioning after your poll budget is not stuck: Sakawave asks the network on a widening backoff for roughly two hours, then fails and refunds by itself.
When you bought more than one
quantity above 1 produces one order and up to that many eSIMs, and the network can fill part of an order. Read profileIds as the truth about how many you actually got:
5. Hand it to the handset
One call, three forms of the same credential.409 esim_profile_not_ready. That is a timing answer, not a failure: the profile is still pending.
6. Run the eSIM for the rest of its life
Cancel and revoke are not two words for the same thing. Cancel returns the money because nobody used the eSIM; revoke returns nothing because the network has already been paid for the data. Revoking asks for
{"confirm": true} for exactly that reason.
Top up instead of selling a second eSIM
A top-up extends the profile already on the phone — same ICCID, nothing to reinstall. It must cover the same destination, and the plan must itself support being a top-up:409 esim_topup_region_mismatch; a plan that cannot extend anything is 409 esim_package_not_topupable.
Sell the top-up on
esim.usage.low — under about 100 MB left — rather than waiting for the allowance to run out. A depleted eSIM is refused with 409 esim_profile_not_topupable, so the offer has to reach the customer while data is still flowing.Reaching the handset
If the plan reportssupportsPlatformSms, you can push one 160-character message to the eSIM — ten per hour per eSIM, and 429 esim_message_rate_limited past that. Accepted means the provider took it, not that anyone read it: an eSIM that was never installed swallows it silently. The message log is what support reads back.
It is a lifeline for a traveller with no other connectivity, not a notification channel. Use your own push and email for everything else.
7. Reconcile
Orders carry both figures, and they answer different questions:paid is what left the account, in the account’s currency — the only figure a bank statement will agree with. price is the USD list price, for reconciling against the catalog. Refunds have already happened; they are entries to record, not exceptions to chase. Then reconcile the transaction alongside everything else.
What goes wrong, and what to do
Next
- Setup and configuration — the quote window, idempotency, refunds, rate limits.
- Use cases — the same flows written as recipes.
- Webhook events — every
esim.*payload, and what is deliberately not in one. - Launch checklist — before you point this at production.