Integration patterns
These patterns cover the decisions most travel agencies and travel desks face when they add eSIMs to a booking flow. None of them is required. Each one avoids a problem that is hard to fix later.
Book, then issue
Section titled “Book, then issue”Place the eSIM order only once your own booking is certain, and tie the two together with one reference.
- The customer picks a package. You show the price you have decided to charge.
- The customer pays you, or the booking is approved on account.
- You save the booking with a state such as
esim_pending. - You call
POST /v1/orderswithcustomer_refset to your booking number andIdempotency-Keyset to the same value. - On
completed, you save the order id and the ICCIDs, deliver the eSIM and mark the bookingesim_issued.
Save your booking before you call the API. If your server stops between the call and the save, you still know an order may exist, and the idempotency key lets you ask again safely.
Check the price before you order. Call GET /v1/packages/{id} if your cached copy is old, and compare the order’s total with what you expected. An order is charged at the price at the moment you place it.
Handling processing
Section titled “Handling processing”Most orders are completed in the response. POST /v1/orders waits up to about 20 seconds for the eSIMs, so set your HTTP client timeout to at least 30 seconds. An order that is not finished by then comes back as processing. Its esims list holds the eSIMs issued so far and can be empty. Do not treat that as a failure and do not order again.
- Save the order id and show the customer a “your eSIM is being prepared” state.
- Wait for the
order.completedororder.failedwebhook.order.completedis also sent for apartially_completedorder, so readstatusin the event. - As a backstop, fetch
GET /v1/orders/{id}every few seconds at first, then less often. - When the order is final, deliver the eSIMs or tell the customer it failed.
An order that makes no progress for 15 minutes is settled automatically: the eSIMs that were not issued are marked as failed and refunded, and the matching event is sent. If you retry the POST with the same Idempotency-Key while a live order is processing, you get the order as it is now.
Handle every final status:
| Status | What to do |
|---|---|
completed |
Deliver every eSIM in esims. |
partially_completed |
Deliver the eSIMs in esims. There are fewer than quantity. refunded shows what went back to your balance for the rest. Place a new order, with a new idempotency key, for the units that are missing. |
failed |
Nothing was issued and refunded shows what went back to your balance. Tell the customer, or try again with a new idempotency key. |
cancelled |
The order was cancelled in the Business portal or by eSIMify support, and refunded shows the amount returned. Treat its eSIMs as not usable. No webhook is sent: you see it when you fetch the order. |
You can practise this in the sandbox with the test_processing and test_order_failed triggers. In the sandbox a replay of a processing order returns the first response again, so fetch the order instead.
Reconciling with customer_ref
Section titled “Reconciling with customer_ref”customer_ref is your handle on everything eSIMify holds for a sale. It is copied from the order to each eSIM and appears in events.
- Find an order from a booking:
GET /v1/orders?customer_ref=booking-84512. - Find the eSIMs of a booking:
GET /v1/esims?customer_ref=booking-84512. - Daily check: list yesterday’s orders with
created_afterandcreated_before, and compare the count and the sum oftotallessrefundedwith your own records.created_afterincludes its own instant andcreated_beforedoes not, so two windows that share a boundary never miss or repeat an order. Top-ups are not in this list: read them withGET /v1/esims/{iccid}/topups. - Orders you did not place: orders your staff place in the Business portal appear in
GET /v1/ordersand send events too. Ignore the ones whosecustomer_refyou do not recognise. - Missed webhooks: list
GET /v1/eventsand process any eventidyou have not stored. Events are kept for 30 days. Live eSIM events are stored only while you have an enabled live endpoint, so check eSIM state withGET /v1/esimsas well.
Keep customer_ref unique for each sale and free of personal data. A booking number is ideal. A name or an email address is not.
Caching the catalogue
Section titled “Caching the catalogue”The catalogue changes slowly, and each key has a rate limit. Do not call the API on every page view.
- Copy the catalogue into your own database at least once a day. Page through
GET /v1/packageswithlimit=100untilhas_moreisfalse. A full copy is not a snapshot: if the catalogue changes while you page, a package can be missed or seen twice, so key your copy onid. - Serve your destination pages and search from your copy.
- Store
priceas a decimal, with its currency. - Before you place an order, the order itself is the final check: a package that is no longer on sale gives
package_not_found. Refresh your copy when you see it. - The
idlisted for a destination, data size and validity can change. An id you stored keeps working while that package is on sale. Readdata_mbandvalidity_daysfrom the package, not from its name. - Use
GET /v1/countriesto build the destination list, andpackages_countto hide destinations with nothing to sell.
Low balance
Section titled “Low balance”An order is refused with 402 insufficient_balance when your balance does not cover it. Avoid meeting that error in front of a customer.
- Set a low-balance threshold in the Business portal under Settings. Without one,
balance.lowis never sent. - Subscribe to the
balance.lowwebhook and alert the person who can add funds. It is sent once each time your balance crosses the threshold. - Read
balance_afteron each order and raise your own alert at a level that suits your volume. It is the balance right after the order was charged. If part of the order is refunded, your balance is higher byrefunded. - For a large batch, check
GET /v1/balancefirst and compare it with the sum of the prices. - When a
402does happen, keep the booking inesim_pending, alert your team, and send the same request again once funds have been added in the portal under Wallet. Use the same idempotency key.
Retries
Section titled “Retries”Networks fail. A retry is only safe when it cannot buy twice.
- Send an
Idempotency-Keyon everyPOST, and derive it from your own data, not from a random value made at send time. - On a timeout, a dropped connection,
429,500or503, send exactly the same request again with the same key. - After an order or top-up with the status
failed, the same key returns that failed result again. To try the purchase again, use a new key. - On
409 request_in_progress, the first request is still running. Wait a moment and send it again. - Wait longer between each attempt: for example 1, 2, 4 and 8 seconds. On
429, wait forRetry-After. - Give up after a few attempts, leave the booking in
esim_pendingand alert your team. The key stays valid for at least 24 hours, so a later retry is still safe within that time. - A replayed response has the header
Idempotent-Replayed: trueand status200. It is the first response as it was sent, so fetch the order if you need its current state.
See Idempotency for the full rules.
Ordering for a group
Section titled “Ordering for a group”One order can hold up to 50 eSIMs of the same package. A large order takes longer and is more likely to come back as processing. For a group that needs different packages, place one order for each package. Give each order its own idempotency key, such as booking-84512-th and booking-84512-ae, and the same customer_ref if you want to find them together.

