Getting Order Results from FoxReload — Polling and Building Your Own Notification Layer
FoxReload does not send webhooks. There is no X-FoxReload-Signature header, no HMAC callback endpoint, and no order.* event stream. To get order results — including the delivered codes — you poll GET /api/orders/{order_id}. This guide explains the polling pattern in detail and shows how to build a webhook-like push notification layer in your own backend so downstream consumers (your storefront, your bot, your fulfilment queue) can react to order completion without polling themselves.
1. The polling contract
When status == "completed", each items[].externalData is an array of delivered codes or activation data. An items[].error field may hold a per-item error string.
| Status | Meaning | Your action |
|---|---|---|
active |
Awaiting payment | Wait or pay via /api/orders/{id}/pay |
paid |
Payment confirmed | Wait |
processing |
Supplier fulfilment in flight | Keep polling |
completed |
Codes in externalData | Read and deliver codes |
cancelled |
See cancelReason | Handle gracefully |
failed |
Fulfilment failed | Alert ops |
2. Polling implementation
async function waitForCompletion(
orderId: string,
apiKey: string,
maxWaitMs = 600_000,
): Promise<Order> {
const start = Date.now();
let delay = 5_000; // start at 5 s
while (Date.now() - start < maxWaitMs) {
const res = await fetch(
`https://public-api.foxreload.com/api/orders/${orderId}`,
{ headers: { 'X-API-Key': apiKey } },
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const order = await res.json();
if (['completed', 'cancelled', 'failed'].includes(order.status)) {
return order;
}
await sleep(delay);
delay = Math.min(delay * 1.5, 30_000); // back off up to 30 s
}
throw new Error('Order did not complete within timeout');
}
Practical schedule: poll every 5 seconds for the first 60 seconds, then every 15–30 seconds up to 10 minutes. If the order is still not terminal after that, alert your operations team.
3. Avoid duplicate orders on retry
FoxReload has no idempotency keys. If you receive a timeout or network error after calling POST /api/orders, do not immediately create a new order. First call GET /api/orders?limit=10 to check whether the order was actually created. If it exists, poll it; if it does not, it is safe to create a new one.
4. Building your own async notification layer
Your internal consumers (Telegram bot delivery, email sender, fulfilment queue) should not each poll FoxReload independently. Instead, run a single polling worker per order that publishes results to a message queue or emits events that your internal services subscribe to. This is a clean, reliable pattern:
POST /api/orders ──▶ Your backend creates order
│
▼
Polling worker (single process per order)
polls GET /api/orders/{id} with backoff
│
▼ (on completed)
Internal event bus (Redis Streams / SQS / BullMQ)
publish { orderId, status, codes }
│
┌────┴────────────┐
▼ ▼
Telegram delivery Email sender
This gives you a webhook-like push model for your internal systems while correctly implementing the FoxReload polling contract.
5. Persisting and delivering codes
When status is completed, read items[].externalData and store each code in your database with the order ID, timestamp, and customer reference before delivering. Never deliver codes without first persisting them — a delivery failure after persistence is recoverable; a delivery before persistence is not.
for (const item of order.items) {
for (const data of item.externalData ?? []) {
await db.codes.insert({
orderId: order.id,
productId: item.product.id,
code: data.code ?? JSON.stringify(data),
deliveredAt: null,
});
}
}
await deliverCodesToCustomer(order.id);
await db.orders.update(order.id, { status: 'delivered', deliveredAt: new Date() });
6. Alerting on stalled orders
Add a scheduled job that checks for orders stuck in processing for more than 10 minutes and alerts your on-call team. This surfaces supplier-side delays before they become customer support issues.
// Run every 5 minutes
const stalled = await db.orders.findAll({
status: ['paid', 'processing'],
createdAt: { lt: new Date(Date.now() - 10 * 60_000) },
});
for (const order of stalled) {
await alertOps(`Order ${order.id} has been processing for >10 min`);
}
FoxReload provides the order result via polling; everything above that — real-time delivery to your customers, internal event buses, stall alerts — is your infrastructure to build. The pattern is straightforward and robust once it is in place. Request access to get started.
