Testing your integration
This is a test plan for the sandbox. It needs no knowledge of eSIMs. Work through it top to bottom and tick each check. Every check says what to do and what you should see.
Before you start
Section titled “Before you start”You need:
- a test key (
sk_test_...) and your base URL, from the Business portal under Developers - a test-mode webhook endpoint on your staging server, added on the Webhooks tab, and its signing secret
- a way to see what your server received and stored
Run the checks through your own system where you can, not only with curl. The point is to test your code. The commands below show the API call behind each check.
export ESIMIFY_API_URL="<the base URL from the Developers page>"export ESIMIFY_API_KEY="sk_test_..."Access
Section titled “Access”1. A good key works.
curl -i "$ESIMIFY_API_URL/v1/balance" -H "Authorization: Bearer $ESIMIFY_API_KEY"- Status
200. The body has"mode": "test". The response has anX-Request-Idheader.
2. A bad key is refused.
curl -i "$ESIMIFY_API_URL/v1/balance" -H "Authorization: Bearer sk_test_wrong"- Status
401. The body has"code": "invalid_api_key"and arequest_id. - Run it once or twice only. Sixty bad keys in 10 minutes block your address for the rest of those 10 minutes.
- Your system reports a configuration problem and does not retry forever.
Catalogue
Section titled “Catalogue”3. The destination list loads. Call GET /v1/countries.
- Your destination picker shows the names from the response.
4. Packages load for one country, across pages. Call GET /v1/packages?country=TH&limit=2, then call again with cursor set to the next_cursor you received.
- The second page has different packages from the first.
- Your system stops when
has_moreisfalse. - Prices are shown with two decimal places and are not rounded wrongly.
5. An unknown package is handled. Call GET /v1/packages/pkg_999999999.
- Status
404with"code": "package_not_found". Your system shows a sensible message.
Orders
Section titled “Orders”6. A normal order completes. Place an order for one package from check 4, with a fresh booking number as customer_ref and as Idempotency-Key.
- Status
201.statusiscompleted.esimshas one entry whose ICCID starts with8999999. -
balance_afteris lower than before bytotal. - Your booking is marked as issued and stores the order id and the ICCID.
7. A repeated order is not bought twice. Send exactly the same request again, with the same Idempotency-Key.
- Status
200. The response has the headerIdempotent-Replayed: true. The orderidis the same as in check 6. - Your system still shows one eSIM for the booking.
8. A reused key with a different body is refused. Send the same Idempotency-Key with a different quantity.
- Status
409with"code": "idempotency_key_reused".
9. A missing key is refused. Send an order with no Idempotency-Key header.
- Status
400with"code": "idempotency_key_required".
10. A bad quantity is refused. Send an order with "quantity": 51.
- Status
400with"code": "invalid_request"and"param": "quantity".
11. An order for several eSIMs works. Send an order with "quantity": 3.
-
esimshas three entries with three different ICCIDs. Your system delivers three separate eSIMs.
12. Low balance is handled. Send an order with customer_ref set to test_insufficient_balance-001.
- Status
402with"code": "insufficient_balance". - The customer is not shown an eSIM. Your team is alerted. The booking can be completed later.
13. A failed order is handled. Send an order with customer_ref set to test_order_failed-001.
- Status
201. The order hasstatusfailed, an emptyesimslist andrefundedequal tototal. - An
order.failedwebhook arrives. Your system tells the customer and does not show an eSIM.
14. A slow order is handled. Send an order with customer_ref set to test_processing-001.
- The response has
statusprocessing. Your system shows a waiting state, not an error, and does not order again. - Between 10 and 25 seconds later an
order.completedwebhook arrives and your system delivers the eSIM. -
GET /v1/orders/{id}now showscompleted.
15. An outage is handled. Send an order with customer_ref set to test_supplier_down-001.
- Status
503with"code": "supplier_unavailable". Your system retries with the sameIdempotency-Key, then gives up cleanly and alerts your team.
In live you are more likely to see check 13, a failed order, than this response. Handle both.
Delivery
Section titled “Delivery”16. The eSIM can be shown. Open the eSIM from check 6 in your product.
- The QR code image from
qr_code_urlis displayed. - On a phone, the one-tap link from
install_linksis offered for the right platform. - The SM-DP+ address and the activation code are shown for manual entry, each with a copy button.
- The customer can find the same screen again later.
A sandbox eSIM cannot be installed. This check is about your screens and emails.
Webhooks
Section titled “Webhooks”17. The test event arrives. Choose Send test event on your webhook endpoint in the portal.
- The portal shows the result: delivered, with status
200. A test event is not written to the delivery log. - Your server received an event with
"type": "ping"and accepted its signature.
18. A forged request is refused. Send your webhook endpoint a POST with a valid-looking body and a wrong X-Esimify-Signature. Then send one with no signature header at all.
- Your server answers
400both times and stores nothing.
19. eSIM events are handled. For the eSIM from check 6, call the simulate endpoint three times, in this order: activated, usage_80, usage_100. Use a new Idempotency-Key each time.
- Your server receives
esim.activated,esim.usage_80andesim.usage_100. - After each one, your product shows the right state: connected, running low, out of data.
-
GET /v1/esims/{iccid}shows the matchingstatusanddatavalues:active,active,depleted. - Sending
activateda second time changes nothing and sends no webhook.
20. A repeated event is ignored. Send your webhook endpoint the same request twice: replay a body and its headers that you captured, within 5 minutes. Or make your server answer 500 once, so that the delivery is retried.
- Your server answers
2xxboth times and acts on the event once.
21. A missed event is recovered. Stop your webhook server, place an order, then start the server again.
- The delivery log shows the delivery as retrying.
- Your reconciliation job finds the event with
GET /v1/events, or the retry arrives, and the order is handled once.
22. Events are put in order. Look at the sequence of the events you received in check 19.
- Each one is higher than the one before. Your system uses
sequence, not arrival order, to decide which event is newer.
Top-ups
Section titled “Top-ups”23. A top-up works. For the eSIM from check 6, call GET /v1/esims/{iccid}/topup-packages, then POST /v1/esims/{iccid}/topups with one of the packages.
- Status
201. The response is a top-up withstatuscompleted. - A
topup.completedwebhook arrives. -
GET /v1/esims/{iccid}shows more data than before, andstatusisactiveagain. -
GET /v1/esims/{iccid}/topupslists the top-up.
24. A failed top-up is handled. Send a top-up with customer_ref set to test_order_failed-001.
- Status
201, and the top-up hasstatusfailed. Your system checksstatus, tells the customer nothing was added, and does not show more data.
25. An ended plan is handled. For the eSIM from check 6, simulate expired. Then try to top it up again.
- Your server receives
esim.expiredand your product shows the plan as ended. - The top-up is refused with
409and"code": "esim_not_topupable". Your product does not offer a top-up for this eSIM.
Separation of test and live
Section titled “Separation of test and live”26. Test data stays in test. If you already have a live key, call GET /v1/orders/{id} with it, using a sandbox order id.
- Status
404with"code": "order_not_found".
Sign-off
Section titled “Sign-off”- Every check above passes.
- The Going live checklist is complete.
- The request ids of any failures you could not explain have been sent to support@esimify.in.

