G-Starlink Hub Partner API -- Quick Start Guide
Base URL: https://api.gstarlink.com
API Version: v1
Authentication: OAuth 2.0 Client Credentials
This guide is for agents and resellers integrating with the G-Starlink Hub Partner API to browse products, place orders, retrieve eSIM delivery details, and receive event notifications via webhooks.
Official SDKs
The examples in this guide use raw curl so you can see the underlying HTTP calls, but zero-dependency, single-file SDKs are available for four languages. They handle OAuth token management and webhook signature verification for you:
| Language | File | Requirements |
|---|---|---|
| TypeScript / JavaScript | sdk/esim-atlas-sdk.ts | Node.js 18+ or modern browsers |
| Python | sdk/esim_atlas_sdk.py | Python 3.8+ (stdlib only) |
| PHP | sdk/esim-atlas-sdk.php | PHP 8.0+ (cURL + JSON extensions) |
| Ruby | sdk/esim_atlas_sdk.rb | Ruby 3.0+ (stdlib only) |
See the SDK README for installation, quick starts, and the full method reference.
Service Scope Disclaimer
Our guarantee covers the recharge face value only. For prepaid SIM products and recharges, we guarantee that the purchased face value is credited to the SIM (e.g. a $39 recharge credits $39). The plan inclusions attached to that face value — data allowance, bonus data, calls, validity — are set solely by the carrier and may change at any time without notice.
Example: a carrier may advertise a $39 prepaid plan with 65GB, composed of 25GB standard data plus 40GB promotional bonus data. The bonus portion is a carrier promotion that can shrink, grow, or disappear from one month to the next. We accept no responsibility for differences between advertised and delivered plan inclusions. Always verify current inclusions on the carrier's official website before resale to your customers.
Table of Contents
- Getting API Credentials
- Sandbox Environment
- Authentication
- Browse Products
- Create an Order
- Get eSIM Delivery Details
- Check eSIM Status
- Webhooks
- End-to-End Example
- Status Lifecycle
- Error Handling
- Rate Limits
- Full API Reference
0. Getting API Credentials
You issue your own keys from the Partner Portal. We set up your account and invite you; everything after that is self-service.
- We create your partner account and email you an invitation. Set a password and sign in at https://hub.gstarlink.com/dashboard/partner
- Under API Keys, generate a key. You get a
client_id(starting withsk_test_) and aclient_secret. Copy the secret immediately — it is shown once and cannot be retrieved, only replaced. - Use these credentials to obtain an access token (see Authentication below)
- Build and verify your integration against the sandbox
- When you are ready to go live, ask your account manager to enable production.
Once they do, you generate your own
sk_live_*key from the same screen.
The portal is also where you register webhook endpoints, watch your API usage, and top up the wallet your orders are charged to.
Keys can be revoked and regenerated from the portal at any time.
1. Sandbox Environment
G-Starlink Hub provides a sandbox environment for testing your integration without affecting production data or incurring charges.
How Sandbox Works
- Sandbox uses the same base URL:
https://api.gstarlink.com - Sandbox API keys use the
sk_test_*prefix; production keys usesk_live_* - Generate sandbox keys yourself in the Partner Portal, under "API Keys"
- Orders created with sandbox credentials are marked as test orders
(
"test": truein metadata) and are auto-fulfilled within seconds -- no payment step, no real inventory GET /orders/{id}/esimsreturns synthetic test eSIMs for sandbox orders: deterministic ICCIDs (prefix8999), a sandbox SM-DP+ address, andis_test: trueon every record. These can never activate on a deviceorder.createdandorder.fulfilledwebhooks fire for sandbox orders, so the full order -> webhook -> eSIM retrieval loop is testable end to end
Testing Checklist
- Generate a sandbox API key (
sk_test_*) - Obtain an access token using sandbox credentials
- Browse products (same catalog as production)
- Register a webhook subscribed to
order.createdandorder.fulfilled - Create a test order -- it returns
pending_payment, then auto-fulfils; theorder.fulfilledwebhook arrives within seconds - Fetch
GET /orders/{id}/esimsand process the synthetic test eSIMs - Once integration is verified, switch to production credentials (
sk_live_*)
Sandbox Limitations
- eSIMs are synthetic (
is_test: true) and cannot be installed on devices - Physical SIM activations (
POST /activations) are NOT simulated -- they enter the real activation queue, so only submit real SIMs you own - Payment/wallet deductions are simulated (sandbox orders skip payment)
2. Authentication
The API uses OAuth 2.0 Client Credentials flow. You need a client_id and client_secret issued by your G-Starlink Hub account manager.
Step 1: Obtain an Access Token
curl -X POST https://api.gstarlink.com/api/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"scope": "read:products write:orders read:orders read:esims write:webhooks read:webhooks"
}'
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read:products write:orders read:orders read:esims write:webhooks read:webhooks"
}
Step 2: Use the Token
Include the token in the Authorization header for all subsequent requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Tokens expire after 1 hour (3600 seconds). Request a new token when the current one expires.
Available Scopes
| Scope | Description |
|---|---|
read:products | Browse product catalog |
read:orders | View orders |
write:orders | Create orders |
read:esims | Access eSIM delivery details |
read:webhooks | List webhooks |
write:webhooks | Create/manage webhooks |
3. Browse Products
List All Products
curl -X GET "https://api.gstarlink.com/api/v1/products?limit=10®ion=Asia" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"sku": "ASIA-5GB-30D",
"name": "Asia 5GB - 30 Days",
"description": "Data-only eSIM for 12 Asian countries",
"region": "Asia",
"coverage": ["JP", "KR", "TH", "SG"],
"data_amount_mb": 5120,
"validity_days": 30,
"price_cents": 1999,
"currency": "USD",
"type": "data_only",
"status": "active"
}
],
"pagination": {
"has_more": true,
"next_cursor": "eyJjcmVhdGVkX2F0Ijo..."
}
}
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 20 | Results per page (max 100) |
cursor | string | -- | Pagination cursor from previous response |
region | string | -- | Filter by region (e.g., Asia, Europe) |
type | string | -- | data_only or voice_data |
status | string | active | active, inactive, or out_of_stock |
sort_by | string | created_at | created_at, price_cents, or name |
sort_order | string | desc | asc or desc |
Get a Single Product
curl -X GET "https://api.gstarlink.com/api/v1/products/550e8400-e29b-41d4-a716-446655440001" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
4. Create an Order
Single Order
curl -X POST https://api.gstarlink.com/api/v1/orders \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: YOUR_UNIQUE_IDEMPOTENCY_KEY" \
-d '{
"line_items": [
{
"product_id": "550e8400-e29b-41d4-a716-446655440001",
"quantity": 5
}
]
}'
Response:
{
"id": "order-uuid-here",
"order_number": "ORD-20260413-ABC12",
"status": "pending_payment",
"total_amount_cents": 9995,
"currency": "USD",
"created_at": "2026-04-13T10:00:00Z"
}
Important: Always include an Idempotency-Key header (UUID recommended). Sending the same key twice returns the original order instead of creating a duplicate.
Note: Some products require activation scheduling (e.g., eSIM with EID push delivery). For these products, include
activation_requestsin your order. Check the product'sdelivery_methodfield -- if it'ssupplier_emailoreid_push, activation info is required.
Bulk Order (E-commerce Integration)
Create up to 100 orders in a single request:
curl -X POST https://api.gstarlink.com/api/v1/orders/bulk \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orders": [
{
"external_order_id": "SHOP-10001",
"line_items": [
{ "product_id": "PRODUCT_ID_1", "quantity": 1 }
],
"customer": {
"email": "customer1@example.com",
"name": "Alice"
}
},
{
"external_order_id": "SHOP-10002",
"line_items": [
{ "product_id": "PRODUCT_ID_2", "quantity": 2 }
],
"customer": {
"email": "customer2@example.com",
"name": "Bob"
}
}
]
}'
Each order in the batch is processed independently. The response includes per-order success/failure status.
List Orders
curl -X GET "https://api.gstarlink.com/api/v1/orders?limit=20" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Get Order Details
curl -X GET "https://api.gstarlink.com/api/v1/orders/ORDER_ID" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
5. Get eSIM Delivery Details
After an order is fulfilled, retrieve the eSIM activation codes and QR codes:
curl -X GET "https://api.gstarlink.com/api/v1/orders/ORDER_ID/esims" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response:
{
"order_id": "order-uuid-here",
"esims": [
{
"id": "esim-uuid",
"iccid": "8944501234567890123",
"activation_code": "LPA:1$smdp.example.com$ACTIVATION_CODE",
"qr_code_url": "https://api.gstarlink.com/qr/esim-uuid.png",
"status": "inactive"
}
],
"count": 5
}
Key fields for end-user delivery:
activation_code-- The LPA string the user enters manually on their deviceqr_code_url-- A scannable QR code image URL for direct installation
6. Check eSIM Status
Query the real-time status of a specific eSIM by its ICCID:
curl -X GET "https://api.gstarlink.com/api/v1/esims/8944501234567890123/status" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Check Order Activation Status
For orders with supplier-delivered products, check the activation progress:
curl -X GET "https://api.gstarlink.com/api/v1/orders/ORDER_ID/activation-status" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response:
{
"order_id": "order-uuid",
"order_number": "ORD-20260413-ABC12",
"has_scheduled_activations": true,
"total_activations": 1,
"pending_count": 0,
"submitted_count": 1,
"activated_count": 0,
"failed_count": 0,
"activations": [
{
"item_id": "order-item-uuid",
"product_name": "Europe 10GB - Orange",
"status": "submitted",
"customer_email": "customer@example.com",
"activation_date": "2026-05-01",
"submitted_at": "2026-04-13T10:30:00Z"
}
]
}
Activation statuses: pending | submitted | confirmed | activated | failed | cancelled
6b. Check Wallet Balance
curl -X GET "https://api.gstarlink.com/api/v1/balance" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response:
{
"balances": [
{
"currency": "USD",
"balance_cents": 250000,
"last_transaction_at": "2026-07-13T08:15:00Z"
}
],
"count": 1
}
Balances are held per currency. Orders are charged in the product's currency, so make sure the matching wallet is funded.
7. Webhooks
Register a Webhook
curl -X POST https://api.gstarlink.com/api/v1/webhooks \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/esim-atlas",
"events": [
"order.created",
"order.fulfilled",
"esim.activated",
"activation.activated",
"activation.failed"
]
}'
Response:
{
"webhook": {
"id": "wh_uuid",
"url": "https://your-app.com/webhooks/esim-atlas",
"events": ["order.created", "order.fulfilled", "esim.activated", "activation.activated", "activation.failed"],
"status": "active"
},
"secret": "whsec_..."
}
Save the secret immediately. It is only shown once and is required for signature verification.
Available Events
| Event | Description |
|---|---|
order.created | Order has been created |
order.fulfilled | eSIMs assigned to order |
order.cancelled | Order was cancelled |
esim.activated | eSIM activated on device |
esim.expired | eSIM has expired |
payment.completed | Payment succeeded |
payment.failed | Payment failed |
activation.submitted | Activation request sent to supplier |
activation.confirmed | Supplier confirmed receipt |
activation.activated | Activation completed |
activation.failed | Activation failed |
Webhook Payload Examples
Each webhook delivery sends a JSON payload with the event type, a unique event ID, a timestamp, and the event data.
order.created
{
"event": "order.created",
"event_id": "evt_abc123",
"timestamp": "2026-04-13T10:00:00Z",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"order_number": "ORD-20260413-A1B2C",
"status": "pending_payment",
"total_price_cents": 1500,
"currency": "USD",
"line_items": [{ "product_id": "...", "quantity": 1 }]
}
}
order.fulfilled
{
"event": "order.fulfilled",
"event_id": "evt_def456",
"timestamp": "2026-04-13T10:05:00Z",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"order_number": "ORD-20260413-A1B2C",
"status": "fulfilled",
"esims": [
{
"iccid": "8901234567890123456",
"activation_code": "LPA:1$smdp.example.com$MATCHING_ID",
"qr_code_url": "https://api.gstarlink.com/qr/esim-uuid.png"
}
]
}
}
activation.activated
{
"event": "activation.activated",
"event_id": "evt_ghi789",
"timestamp": "2026-04-13T10:10:00Z",
"data": {
"id": "75f7b6f8-1234-5678-9abc-def012345678",
"iccid": "8961030000012345678",
"carrier": "Lycamobile",
"mobile_number": "0402033268",
"status": "completed"
}
}
activation.failed
{
"event": "activation.failed",
"event_id": "evt_jkl012",
"timestamp": "2026-04-13T10:10:00Z",
"data": {
"id": "75f7b6f8-1234-5678-9abc-def012345678",
"iccid": "8961030000012345678",
"carrier": "Lycamobile",
"error_type": "carrier_error",
"error_message": "ID verification failed",
"status": "failed"
}
}
Webhook Delivery
- Retries: Up to 5 attempts with exponential backoff (1s, 2s, 4s, 8s, 16s)
- Timeout: Your endpoint must respond within 10 seconds
- Expected response: HTTP 2xx status code
Signature Verification (Node.js)
Every webhook request includes an X-Webhook-Signature header with the format t=TIMESTAMP,v1=SIGNATURE.
const crypto = require('crypto');
function verifyWebhookSignature(payload, signatureHeader, secret) {
// Parse the signature header
const parts = signatureHeader.split(',');
const timestampPart = parts.find(p => p.startsWith('t='));
const signaturePart = parts.find(p => p.startsWith('v1='));
if (!timestampPart || !signaturePart) {
return false;
}
const timestamp = timestampPart.split('=')[1];
const receivedSignature = signaturePart.split('=')[1];
// Reject requests older than 5 minutes (replay attack prevention)
const age = Math.abs(Date.now() / 1000 - parseInt(timestamp, 10));
if (age > 300) {
return false;
}
// Compute expected signature: HMAC-SHA256(timestamp + "." + payload, secret)
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
);
}
// Usage in an Express handler
app.post('/webhooks/esim-atlas', (req, res) => {
const payload = JSON.stringify(req.body);
const signature = req.headers['x-webhook-signature'];
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
console.log('Received event:', event.type);
// Process the event...
res.status(200).send('OK');
});
List Webhooks
curl -X GET "https://api.gstarlink.com/api/v1/webhooks" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
8. End-to-End Example
Complete workflow: authenticate, find a product, place an order, and retrieve the eSIM.
#!/bin/bash
# G-Starlink Hub Partner API — Complete Integration Example
# Replace YOUR_CLIENT_ID and YOUR_CLIENT_SECRET with real credentials
BASE_URL="https://api.gstarlink.com"
# Step 1: Get access token
echo "=== Step 1: Authenticate ==="
TOKEN=$(curl -s -X POST "$BASE_URL/api/v1/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}' | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
echo "Token: ${TOKEN:0:20}..."
# Step 2: Browse products (find Japan eSIM)
echo "=== Step 2: Find Products ==="
curl -s "$BASE_URL/api/v1/products?region=Asia&limit=3" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool | head -30
# Step 3: Get a specific product ID (use the first result)
PRODUCT_ID=$(curl -s "$BASE_URL/api/v1/products?region=Asia&limit=1" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import sys,json; print(json.load(sys.stdin)['data'][0]['id'])")
echo "Product ID: $PRODUCT_ID"
# Step 4: Check inventory
echo "=== Step 4: Check Availability ==="
curl -s "$BASE_URL/api/v1/inventory/status?product_ids=$PRODUCT_ID" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
# Step 5: Create order
echo "=== Step 5: Create Order ==="
ORDER=$(curl -s -X POST "$BASE_URL/api/v1/orders" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: demo-$(date +%s)" \
-d "{
\"line_items\": [{
\"product_id\": \"$PRODUCT_ID\",
\"quantity\": 1
}]
}")
echo "$ORDER" | python3 -m json.tool
ORDER_ID=$(echo "$ORDER" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "Order ID: $ORDER_ID"
# Step 6: Check order status (poll until fulfilled)
echo "=== Step 6: Check Order Status ==="
curl -s "$BASE_URL/api/v1/orders/$ORDER_ID" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
# Step 7: Get eSIM delivery (only works when order is fulfilled)
echo "=== Step 7: Get eSIM Delivery ==="
curl -s "$BASE_URL/api/v1/orders/$ORDER_ID/esims" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
echo "=== Done ==="
9. Status Lifecycle
Order Status
pending_payment ──> paid ──> fulfilled
│ │
│ ├──> waiting_review ──> fulfilled
│ └──> failed
│
└──> cancelled
| Status | Description |
|---|---|
| pending_payment | Order created, awaiting payment |
| paid | Payment received, fulfillment starting |
| waiting_review | Held for manual review before fulfillment |
| fulfilled | Order complete, eSIM delivered |
| failed | Fulfillment failed (check error details) |
| cancelled | Order cancelled by partner or admin |
How payment and fulfillment work
Production orders are paid from your pre-funded wallet. There is no card or checkout step in the API:
- You top up your wallet by bank transfer (details from your account manager). Once we confirm the funds, your balance is credited.
POST /orderscharges the order total to your wallet atomically and the order is created aspaid. If the balance is too low, the order is not created at all and you get a402 Insufficient Balance(see below).- Fulfillment allocates your eSIMs and the order becomes
fulfilled— theorder.fulfilledwebhook fires andGET /orders/{id}/esimsreturns the delivery details.
Check your balance any time with GET /balance. Every successful order also
returns wallet_balance_after_cents, so you can track headroom without an
extra call.
In sandbox, payment is simulated: orders are never charged to the wallet (no funding required) and auto-fulfil within seconds.
Insufficient balance (402)
{
"type": "https://docs.gstarlink.com/errors/insufficient_balance",
"title": "Insufficient Balance",
"status": 402,
"detail": "Wallet balance is too low for this order. Top up your account and retry.",
"currency": "USD",
"balance_cents": 905,
"required_cents": 8190,
"shortfall_cents": 7285
}
No order is created and no funds are taken — top up and retry the same request.
Activation Status (Physical SIM)
pending ──> processing ──> completed
│
└──> failed ──> manual_required
| Status | Description |
|---|---|
| pending | Activation request submitted, queued for processing |
| processing | Activation is being processed with the carrier |
| completed | SIM activated successfully, mobile number assigned |
| failed | Activation failed (e.g. identity verification issue) |
| manual_required | Could not be completed automatically; our operations team is handling it |
10. Error Handling
All error responses follow the RFC 7807 Problem Details format:
{
"type": "https://docs.gstarlink.com/errors/validation-error",
"title": "Validation Error",
"status": 400,
"detail": "Invalid product_id in line_items[0]",
"requestId": "req_abc123",
"timestamp": "2026-04-13T10:00:00Z"
}
HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad request -- check the detail and type fields |
| 401 | Unauthorized -- invalid or expired token |
| 403 | Forbidden -- insufficient scopes |
| 404 | Not found |
| 409 | Conflict -- duplicate idempotency key with different payload |
| 429 | Rate limit exceeded |
| 500 | Server error |
Token Expiry
When your token expires, the API returns 401. Request a new token and retry:
# 1. Get a new token
curl -X POST https://api.gstarlink.com/api/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}'
# 2. Retry the failed request with the new token
11. Rate Limits
API requests are rate-limited per API key. Current limits:
| Tier | Requests per minute |
|---|---|
| Standard | 60 |
| Premium | 300 |
When rate-limited, the API returns HTTP 429 with a Retry-After header indicating how many seconds to wait.
12. Full API Reference
The complete OpenAPI specification is available at:
- OpenAPI YAML:
/docs/api/openapi.yaml - Partner API v1 Spec:
/docs/api/partner-api-v1.yaml - Official SDKs (TypeScript / Python / PHP / Ruby):
/docs/api/sdk/
Integration Checklist
- Obtain your
client_idandclient_secretfrom your account manager - Request an access token via the OAuth endpoint
- Browse the product catalog and select products to sell
- Create a test order with an idempotency key
- Retrieve eSIM delivery details (QR code + activation code)
- Register a webhook endpoint to receive order and activation events
- Implement webhook signature verification
- Test the full flow end-to-end in sandbox (
sk_test_*keys) - Switch to production keys (
sk_live_*) when ready to go live