Fulfillment Model
How Starship fulfills orders — inventory-first, admin-gated vendor fallback — and what that means for your integration's polling strategy
Fulfillment Model
Starship fulfills orders inventory-first. On every POST /orders, the API checks an in-memory voucher cache. A cache hit means the voucher is delivered synchronously, in the same request. A cache miss means the order is parked in PENDING status, and — critically — Starship does not call the vendor API on your behalf. The vendor is only contacted after explicit admin action.
This design has two direct consequences for your integration:
- Some orders complete in milliseconds, others take hours. The same product can fulfill instantly at 10:00 AM and park in
PENDINGat 10:05 AM, depending on inventory state. Your client code must handle both paths. PENDINGdoes not mean "we're working on it." It means "we're waiting for a human or a future inventory pump." Don't build retry loops that assume the server will eventually push the order through.
The Happy Path: Cache Hit
Every client POST /orders takes exactly one of these two paths at creation time. The inventory cache is refreshed periodically by a background pump — see 'Why inventory misses happen' below.
What the cache hit means for you:
- Your
POST /ordersresponse arrives withstatus: "DELIVERED"already set. - Voucher codes are immediately retrievable via
GET /orders/:id. - The
order.deliveredwebhook fires within seconds — before your retry logic would have tried again.
The Miss Path: PENDING
When inventory is not cached for a given (product_id, denomination, currency) tuple, the order is persisted in PENDING status and does nothing further automatically. No vendor API is called. No background retry is triggered by default.
From here, one of three things happens:
- Inventory arrives and a pump runs. The next 30-minute inventory pump (or a manual admin pump) loads the voucher into cache. An admin then triggers a retry, which fulfills the order from cache — same path as a cache hit.
- Admin triggers vendor fallback. An operator explicitly approves vendor-API fulfillment. Starship calls the vendor (e.g., VoucherKart, Bamboo, PineLabs), receives the voucher, and marks the order
DELIVERED. You receive theorder.deliveredwebhook. - Admin cancels. If the voucher can't be sourced, the order is moved to
CANCELLEDand the wallet is refunded. You receive theorder.cancelledwebhook.
The auto-retry cron is disabled by default (DISABLE_AUTO_ORDER_RETRY=true). Even if enabled, it only re-checks inventory — it never calls vendor APIs automatically. If you have a product category that absolutely must auto-fulfill via vendor, ask your Starship contact about enabling ALLOW_AUTO_VENDOR_FULFILL on specific products.
Why Inventory Misses Happen
The inventory cache is not a "live" lookup against vendors — it's a locally-warmed pool of pre-purchased, pre-validated vouchers. Misses happen for a handful of reasons:
- Cold start. No voucher for this
(product, denom, currency)has been loaded yet. - Burst depletion. A bulk order drained the pool faster than the pump can refill.
- Expired or blocked units. Vouchers past their validity window, or flagged by vendor audit, are removed from the pool.
- New product. A recently added product has no inventory yet — everything will miss until the first pump.
Call the Charges API or Product availability endpoint before order creation to see the live delivery_time signal (INSTANT if cache is warm, DELAYED if not). This is the same signal Starship uses internally to decide the fulfillment path, so what you see is what you get.
Async Orders (Large Quantities)
One wrinkle: for large bulk orders (qty > 50), the API returns HTTP 200 immediately with status: PENDING and no order items yet. Order items are created on the first retry (admin-triggered or inventory-pump-triggered). This prevents a single bulk order from holding the API request open for minutes while Starship allocates dozens of vouchers.
What this means for your client:
- Don't read
order.items[]from the initialPOST /ordersresponse on large orders — it will be empty. - Wait for the
order.deliveredwebhook (or pollGET /orders/:id) to see the allocated vouchers. - Treat
pendingItems == 0as "done" only iffulfilledFromCache > 0. The combination of both tells you the order is complete vs. the order never had anything allocated.
What You Should Build
Design for both paths from day one
Even if most of your orders fulfill from cache, some will go PENDING. A production integration must handle status: PENDING on initial POST without treating it as an error.
Subscribe to order.* webhooks
Webhooks are how you learn about PENDING → DELIVERED transitions without polling every order every minute. See the Webhooks guide.
Use delivery_time to set expectations in your UI
If your cart shows "Delivered instantly" to end users, only show that copy when the product's delivery_time is INSTANT at quote time. For DELAYED products, surface "Delivery within a few minutes to a few hours" so customers aren't surprised.
Reconcile daily
Run a daily job that lists orders in PENDING older than 24 hours and alerts. These are stuck and require human attention; they should not silently accumulate.
Next
- Order Lifecycle — every status and sub-status, and who triggers the transitions
- Delivery Semantics — polling strategy and webhook retry policy
- Create an Order — the real API reference