Order Status Polling and Notification Layer for Digital Goods
Short Answer
FoxReload does not send webhooks. To find out when an order is complete, poll GET /api/orders/{order_id} until status is "completed", "cancelled", or "failed". When completed, items[].externalData contains the delivered codes. For high-volume stores that want event-driven behavior, build a background polling worker that converts status changes into internal events.
Important: FoxReload has no webhooks, no
X-FoxReload-Signatureheader, and noorder.*callback events. There is no sandbox — useisMock: truein order creation for test orders. There are no idempotency keys — if a network error leaves an order status uncertain, checkGET /api/orders/{id}before creating a new one to avoid double-buying.
Key takeaway: Polling is the correct pattern for FoxReload. Most orders complete synchronously within seconds. For the cases that are slower, a background polling loop with exponential backoff gives you reliable async handling without webhooks.
Who This Guide Is For
- Developers integrating the FoxReload API who need to handle async order fulfillment
- Store operators building automated code delivery pipelines
- Engineers who want event-driven delivery but are working with a polling API
How FoxReload Order Fulfillment Works
1. POST /api/orders/
Body: {"items": [{"itemId": "product_01k...", "quantity": 1}]}
→ Response: {"id": "order_01k...", "status": "processing", ...}
2. Poll GET /api/orders/{order_id}
→ {"id": "order_01k...", "status": "processing", ...} (not yet)
→ {"id": "order_01k...", "status": "completed",
"items": [{"externalData": ["XXXXX-YYYYY-ZZZZZ"]}]} (done)
When status == "completed", read items[i].externalData — this is an array of delivered codes or top-up confirmations. If an item has an error, check items[i].error.
Polling vs. Webhooks
| Factor | Polling (FoxReload) | Webhooks (not available) |
|---|---|---|
| Supported by FoxReload | Yes | No |
| Latency | Depends on poll interval (typically <3s) | N/A |
| Implementation | Simple loop | Not applicable |
| High volume handling | Background worker recommended | N/A |
Terminal Order Statuses
| Status | Meaning | Action |
|---|---|---|
completed |
Order fulfilled; codes in items[].externalData |
Deliver codes to customer |
cancelled |
Order cancelled before fulfillment | Refund customer; do not deliver |
failed |
Fulfillment failed | Alert ops; refund if payment was taken |
processing / paid / active |
Still in progress | Keep polling |
Basic Polling Implementation
import time
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://public-api.foxreload.com"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
def poll_order(order_id: str, max_attempts: int = 30) -> dict:
terminal = {"completed", "cancelled", "failed"}
delay = 1.0
for attempt in range(max_attempts):
resp = requests.get(f"{BASE_URL}/api/orders/{order_id}", headers=HEADERS)
resp.raise_for_status()
order = resp.json()
if order["status"] in terminal:
return order
time.sleep(delay)
delay = min(delay * 1.5, 15) # exponential backoff, cap at 15s
raise TimeoutError(f"Order {order_id} did not reach terminal status")
def get_codes(order: dict) -> list[str]:
codes = []
for item in order.get("items", []):
codes.extend(item.get("externalData", []))
return codes
# Usage
order = poll_order("order_01k...")
if order["status"] == "completed":
codes = get_codes(order)
deliver_to_customer(codes)
elif order["status"] == "failed":
alert_ops(order)
High-Volume: Background Polling Worker
For stores processing many orders per minute, run a dedicated background worker instead of blocking in-request polling:
Order creation API call
→ Store order_id in DB with status = 'pending'
→ Return response to customer ("Order placed, processing")
Background worker (runs every 2–5 seconds):
→ Query DB: SELECT * FROM orders WHERE status = 'pending'
→ For each: GET /api/orders/{order_id}
→ If status changed:
- Update DB
- If completed: deliver codes to customer (email / bot / order page)
- If failed: alert ops
This decouples order creation from polling and scales cleanly. Your worker is the notification layer — FoxReload is not involved in pushing events.
Building Your Own Internal Notification Layer
If you want event-driven behavior inside your own system, emit internal events from the polling worker:
# After polling detects a status change:
if new_status == "completed":
internal_queue.publish("order.completed", {
"order_id": order_id,
"codes": get_codes(order),
"customer_id": customer_id,
})
# Separate consumer:
@queue.consumer("order.completed")
def on_order_completed(event):
send_code_to_customer(event["customer_id"], event["codes"])
This is entirely within your own infrastructure. FoxReload does not participate in this event flow.
Avoiding Double Orders (No Idempotency Keys)
FoxReload has no idempotency keys. If a network error makes it unclear whether an order was created, always check before creating a new one:
def safe_create_order(item_id: str, qty: int, your_ref: str) -> dict:
# 1. Check if an order with your internal reference already exists
existing = find_order_by_reference(your_ref) # your DB lookup
if existing:
return poll_order(existing["foxreload_order_id"])
# 2. Create new order
resp = requests.post(
f"{BASE_URL}/api/orders/",
headers=HEADERS,
json={"items": [{"itemId": item_id, "quantity": qty}]}
)
resp.raise_for_status()
order = resp.json()
save_order_reference(your_ref, order["id"]) # persist before polling
return poll_order(order["id"])
Store your internal reference and the FoxReload order ID together before polling.
Handling Stuck Orders
If an order remains in processing beyond your SLA:
- Continue polling with backoff — some fulfillments are legitimately slow
- After your SLA threshold (e.g., 10 minutes), alert your ops team with the order ID
- Do not cancel and re-create automatically — check with FoxReload support first to avoid double-buying
Checklist
- POST /api/orders/ with
X-API-Keyauth andapplication/jsoncontent-type - Store order ID immediately after creation
- Poll GET /api/orders/{order_id} with exponential backoff
- Handle all terminal statuses: completed, cancelled, failed
- Extract codes from items[].externalData on completion
- Check items[].error for per-item failures on completed orders
- Before re-creating on network error, check if order already exists
- For high volume: background polling worker with internal event emission
- Alert on orders stuck in processing beyond SLA
- Test with isMock: true orders before going live
