Test Mode
Use partner tke_test_ or member tke_member_test_ keys on api-testing.tongkhoesim.com. Virtual funds and simulated eSIMs are isolated from real money, stock and invoices. Supported operations depend on the key owner; see the member guide for bulk, webhook and refund-review limitations.
Exercise the complete transaction flow before touching real funds. See the HMAC Signing guide for authentication formulas and the Try-it Console to dispatch test requests directly in your browser.
An administrator grants OpenAPI per member. Open API Keys in the main sidebar, select Live or Test, then create a key; the secret appears once. Both modes share the key limit. Disabling access blocks both modes while still allowing revocation.
Live uses the member’s real VND wallet, without credit. Test uses a separate virtual wallet and simulated eSIMs; add test funds, seed or reset samples in Test Mode. Test orders do not appear in live purchase history.
Bulk and webhooks are unsupported for members in both modes. Refund review requests are live-only; cancel a test eSIM to try a virtual-wallet refund. Test never calls a real provider or issues a real MISA invoice. Unsupported operations return capability.not_supported.
Member API endpointsTwo hosts, split by key
Call the right host for the right key type. Calling the wrong host is rejected at the auth layer, before any business logic runs.
| Host | Base URL | Accepted keys |
|---|---|---|
| Live | https://api.tongkhoesim.com/v1 | tke_live_ / tke_member_live_ |
| Test mode | https://api-testing.tongkhoesim.com/v1 | tke_test_ / tke_member_test_ |
A test key calling the live host, or a live key calling the test host, is rejected with 403 partner.wrong_host — the error body states the correct host to use. Logs never leak the key.
Creating a test key
From the partner dashboard, Test Mode section: create a key with prefix tke_test_. The secret is shown only once, store it immediately. Your organization's FIRST test key automatically calls sample data seeding (POST /v1/testmode/seed) right after creation — no extra step needed.
7 Differences Between Live and Test Mode
| Aspect | Live (api host, tke_live_) | Test Mode (api-testing host, tke_test_) |
|---|---|---|
| Host | api.tongkhoesim.com | api-testing.tongkhoesim.com — wrong host is rejected with 403 |
| Wallet | Actual organization wallet | Dedicated virtual wallet, unlimited top-ups via dashboard or admin adjustment |
| Inventory | Real esim_profiles | Virtual inventory, unlimited mock eSIM generation |
| Provisioning | Real upstream telcos | Deterministic mock: identical inputs yield identical mock ICCID/LPA (LPA:1$testmode-smdp.tongkhoesim.com$…) |
| Usage | Measured for real from the telco | Self-running increase (drift) every few minutes, with 75/90% threshold alerts |
| Webhooks | Real production events | Real test-mode events plus manual fire and forced fail-next |
| Ledger | Real financial ledger | Isolated virtual ledger (append-only, never deleted), never mixed with live ledger |
/v1/testmode/seed
POST /v1/testmode/seed creates a ready-made data set so you have something to call the API against immediately, instead of placing orders from scratch. Idempotent per organization — calling it again does not duplicate data; it returns already_seeded: true.
- A test wallet pre-funded with enough balance
- 3 orders (1 eSIM each) across 3 real packages priced for your organization
- 3 different states: not installed · running at 40% usage · expired
#!/usr/bin/env bash
# Requires: bash + openssl + uuidgen.
KEY_ID="tke_test_REPLACE_WITH_YOUR_KEY_ID"
SECRET="REPLACE_WITH_YOUR_SECRET"
METHOD="POST"
REQ_PATH="/v1/testmode/seed"
BODY='{}'
TS="$(date +%s000)" # epoch milliseconds
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"
curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
--data-raw "$BODY"{
"ok": true,
"data": {
"seeded": true,
"already_seeded": false,
"orders_created": 3,
"esims_created": 3
}
}Self-running usage (drift)
Enabled test eSIMs automatically accumulate usage on a cycle (5 minutes by default) until data runs out or the plan expires, firing esim.usage_threshold webhooks at 75% and 90% — simulating an active subscriber without you having to keep clicking simulate. GET /v1/testmode/status reports whether it is enabled and its current interval.
/v1/testmode/* Endpoints
Only live on host api-testing.tongkhoesim.com, requiring a tke_test_ key. No alias exists at any old path.
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/testmode/status | Readiness, checklist, test key/order/eSIM counts, test wallet, webhook and drift status |
| POST | /v1/testmode/seed | Create sample data (idempotent per organization) |
| POST | /v1/testmode/reset | Soft-delete all test orders/eSIMs, zero the test wallet via an offsetting entry |
| POST | /v1/testmode/esims/{order_item_id}/simulate | Simulate: set usage, change machine state, expire, throttle, force next telco rejection |
| POST | /v1/testmode/webhooks/fire | Immediately fire a sample event ({event_type}) to the test key's webhook endpoint |
| POST | /v1/testmode/webhooks/fail-next | Force the next N webhook deliveries to fail (503) to exercise your retry logic |
| via | When triggered | ref_id representation |
|---|---|---|
open_api | Direct /v1 invocation with a test key: order commit, topup, cancel, or simulation endpoint | api_keys.id of caller key |
simulate | Triggered via the dashboard Test Mode simulator by an org member or admin on behalf | User ID of member or admin |
seed | Seed data created by calling /v1/testmode/seed, no manual order placement | Empty |
/v1/testmode/esims/{id}/simulate
test-mode-onlyPOST /v1/testmode/esims/{order_item_id}/simulate simulates eSIM state transitions: set data used, change machine state, expire now or at a given time, throttle bandwidth, or force the next topup/cancel call to be rejected by a mock telco once.
#!/usr/bin/env bash
KEY_ID="tke_test_REPLACE_WITH_YOUR_KEY_ID"
SECRET="REPLACE_WITH_YOUR_SECRET"
ORDER_ITEM_ID="REPLACE_WITH_ORDER_ITEM_ID"
METHOD="POST"
REQ_PATH="/v1/testmode/esims/$ORDER_ITEM_ID/simulate"
BODY='{"data_used_mb":4096,"status":"enabled"}'
TS="$(date +%s000)"
NONCE="$(uuidgen)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
SIGNING="$METHOD"$'\n'"$REQ_PATH"$'\n'"$TS"$'\n'"$NONCE"$'\n'"$BODY_HASH"
SIG="$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')"
curl -sS -X "$METHOD" "https://api-testing.tongkhoesim.com$REQ_PATH" \
-H "X-TKE-Key: $KEY_ID" \
-H "X-TKE-Timestamp: $TS" \
-H "X-TKE-Nonce: $NONCE" \
-H "X-TKE-Signature: $SIG" \
-H "Content-Type: application/json" \
--data-raw "$BODY"
# No Idempotency-Key required: state mutation is intrinsically idempotent.{
"ok": true,
"data": {
"order_item_id": "018f2b00-0000-7000-8000-000000000001",
"esim_state": "enabled",
"running_package_code": "TKE-VN-30D-X3K9F",
"running_started_at": "2026-07-11T07:00:00Z",
"running_expires_at": "2026-08-10T07:00:00Z",
"data_bytes": 5368709120,
"used_bytes": 4294967296,
"remaining_bytes": 1073741824,
"throttled": false,
"via": "simulate",
"last_via": "simulate",
"mode": "test",
"updated_at": "2026-07-11T09:15:00Z"
}
}Test Mode Error Codes
| Error Code | HTTP | Meaning & Resolution |
|---|---|---|
testmode.live_key_forbidden | 403 | Invoked a /v1/testmode/* endpoint with a live key. Switch to a test key (tke_test_) of the same organization |
testmode.invalid_scenario | 422 | Empty body, no fields provided, or status outside released/installed/enabled/disabled |
testmode.not_test_item | 422 | order_item_id does not belong to a test order — cannot simulate onto live data |
testmode.no_sellable_package | 422 | The organization has no priced package to seed with — key creation still succeeds, only seeding fails |
partner.wrong_host | 403 | A test key called the live host, or a live key called the test host — switch to the correct host named in the error body |
See the complete catalog at the Error Codes page . Standard /v1 auth pipelines apply prior to test-mode logic.
Test Webhooks: Fire and Fail-Next
POST /v1/testmode/webhooks/fire immediately sends a mock event to your registered test key webhook URL without creating a real order. Body: { "event_type": "order.completed", "data": {...} } — omit data and the system builds a sample payload for you.
POST /v1/testmode/webhooks/fail-next { "count": 1 } forces the next N deliveries to fail with a mock 503, then clears itself — use it to observe your own retry behavior without taking down your real receiver.