Skip to main content
POST

Authentication

This endpoint requires HMAC-SHA256 signature authentication. See Authentication guide for details.
Two things to get right, in the order they bite:
  1. The signature covers a SHA-256 of the request body. Serialise the payload to a string once, hash that string, and send that same string — re-serialising for the request changes the bytes and the signature fails.
  2. The path in the signature is the full request path including the /api prefix — /api/v1/api_partner/orders, not /v1/api_partner/orders.
The playground below cannot sign for you. It sends whatever you type, and this endpoint rejects anything unsigned — so filling in a body and pressing send returns 401. That is the endpoint working, not a fault.To use it, generate the signature elsewhere and paste it in:
  1. Build the signature over the exact body string you are about to send — copy it out of the playground first, or paste a body you have already hashed.
  2. Paste the result into X-Esim-Story-Signature, with the same timestamp you signed with in X-Esim-Story-Timestamp.
  3. Send within 5 minutes of that timestamp, and do not edit the body afterwards — either one invalidates the signature.
For checking your own implementation, the test vector is the faster path: it fixes the credentials, timestamp and body, and states the signature they must produce.

Headers

string
required
Your partner access key. Used for authentication.
string
required
HMAC-SHA256 signature of the request. Generated using your secret key. See Authentication guide for signature generation details.
string
required
Unix timestamp in seconds (UTC). Must be within 5 minutes of server time, in either direction.

Request body

string
required
Your order identifier in your system. Must be unique within your partner account. Re-sending the same value is safe — see Idempotency.
array
required
One entry per product you want to order.
One order carries at most 100 eSIMs in total, counted across every entry — there is no separate limit on how many entries or destinations you combine. Split a larger batch across several orders, each with its own external_order_id. Contact support if your volume needs a higher ceiling.
Get option_id values from List products, which always carries the current catalogue and your price. Only option_id and qty go in the payload — there is no field to override the country, duration or data allowance.

Response

A successful request returns 201 Created. 201 means the order was accepted and provisioning was requested. It does not contain the eSIM. That arrives later, one delivery per unit, on your webhook.
array
required
One entry per unit ordered — a product with qty: 2 produces two entries.

Webhooks

The eSIM is not in this response. Once each unit is issued, it arrives as its own signed POST to your webhook URL — see Webhooks for the payload, retries, and how to verify the signature.

Idempotency

external_order_id is the idempotency key. It is unique per partner account, and re-sending a request with an external_order_id you have already used is safe:
  • If the order exists but no eSIMs were issued yet (a previous attempt failed during provisioning), provisioning is retried and no duplicate order items are created.
  • If the order was already fully provisioned, the original topup_id values are returned unchanged. No second eSIM is issued and you are not charged twice.
This makes 422 provisioning failures and 5xx responses safe to retry verbatim. Always retry with the same external_order_id — generating a new one will create a genuinely new order.
Idempotency is scoped to your partner account and to external_order_id alone. Re-sending the same external_order_id with a different products payload returns the original order’s items and silently ignores the new payload. Use a fresh external_order_id for a genuinely different order.

In sandbox

Testing provisioning failures

Sandbox issues an eSIM for every order, so a provisioning 422 never occurs there on its own. To exercise your error handling, start the external_order_id with one of these: It is a prefix match, so append your own suffix to keep each test order unique — SANDBOX-FAIL-001, SANDBOX-FAIL-002, and so on. These prefixes do nothing in production. An order sent there with a name beginning SANDBOX-FAIL is provisioned normally.
The simulation reproduces the aftermath, not just the status code. As in production, the order and its items are created before provisioning is attempted — so a simulated failure leaves an order that exists with no topup_id, and retrying it with the same external_order_id succeeds and issues the eSIM. That fail-then-retry sequence is the part worth rehearsing: it is what a real provider outage looks like from your side.
Webhooks in sandbox arrive on a delay, as in production — see Webhooks in sandbox.

Errors

400 Bad Request

The request body could not be parsed as JSON. The error value is the raw parser message and its exact wording is not stable — do not match on it.
Common causes:
  • Malformed or truncated JSON in the request body
  • Missing or empty request body

401 Unauthorized

Authentication failed. The body is always this shape:
Invalid signature. is the one worth debugging carefully — the Authentication guide lists its causes in order of likelihood, and a test vector to check your signing code against.

422 Unprocessable Entity

Validation or provisioning errors. The body always has an errors array of strings. Invalid request payload — the order is rejected before anything is created, so nothing is charged and no order record exists:
A single invalid entry rejects the whole request — products are never partially ordered. Provisioning failed — the eSIM could not be issued. Four variants, told apart by their prefix. All four are retryable with the same external_order_id:
A 422 from provisioning means the order record was already created but no eSIM was issued. Retry with the same external_order_id — the retry is idempotent and will not create a duplicate order. See Idempotency. A 422 from payload validation creates nothing, so fix the payload and send it as a new request.

429 Too Many Requests

Rate limit exceeded. Check the Retry-After response header (in seconds) before retrying. See Rate Limits.

500 Internal Server Error

Returned for unexpected server-side failures. The response body is not guaranteed to be JSON — do not parse it. Treat any 5xx as retryable with the same external_order_id.

Debugging by symptom

The tables above are indexed by status code. This one is indexed by what you actually see first, including the two failures that return no error at all.
Last modified on October 28, 2026