B2B platform for digital goods

Common Mistakes When Integrating a Digital-Goods Supplier API — 2026

Duplicate orders, lost codes and balance drift — the eight integration mistakes that cost the most, and how to fix each one.

Common Mistakes When Integrating a Digital-Goods Supplier API

Supplier integrations do not break where teams expect them to. The happy path — create order, receive code — takes a day to write. Everything else is the states nobody modelled in version one. Below are the eight failure modes that show up most often in this industry, each with a concrete fix.

1. Retrying without deduplication → duplicate orders

What happens. Your service sends POST /orders. The supplier creates the order and starts fulfilment, but the response is lost on the wire. Your HTTP client retries per its own policy. The supplier sees a fresh request, creates a second order and debits your deposit again. The customer got one code; you paid for two.

Many integrators assume an idempotency header protects them here. Verify that your supplier actually implements one — plenty do not perform server-side dedup, and sending the header "just in case" achieves nothing.

The fix. Deduplicate on your side, always:

  1. Write a local pending record with your own UUID before calling the API.
  2. Call order creation.
  3. On success, store the supplier's order ID against that same record.
  4. On timeout or network error, do not blindly retry — query the order list and look for a match on SKU, quantity and time window.
  5. Create a new order only if no match exists.

The pattern is worked through in detail in our deep dive on idempotency and safe retries.

2. Treating webhooks as a guarantee

What happens. The handler is written as if each event arrives exactly once and strictly in order. In reality webhooks are lost during your own deploys, arrive twice after a sender-side retry, and overtake each other — completed can land before processing.

The result is orders stuck forever in an intermediate state, and codes delivered twice because a redelivery was processed as a new event.

The fix.

  • The handler must be idempotent: a repeated event with the same ID must not change state.
  • Enforce monotonicity: ignore any event describing a state earlier than the one already recorded.
  • Treat the webhook as a hint, not a fact — on receipt, read the order status from the API and record what the API returned.
  • Keep polling as a safety net for orders that have not reached a terminal state in a reasonable time.

If your supplier offers no webhooks at all, polling becomes the only mechanism you have — how to build it properly is covered in tracking order status.

3. Retrying without backoff or jitter

What happens. The supplier returns 429 or 503. Your client retries after a fixed one second, across ten threads, for every stuck order simultaneously. You convert a brief degradation on their side into a sustained storm, prolong it, and risk a long rate-limit ban.

The fix.

  • Exponential backoff with a ceiling, plus random jitter so clients do not synchronise.
  • Retry only what is safe to retry: GET always, order creation only under the scheme in point 1.
  • Distinguish error classes: 4xx from bad input is pointless to retry, 429 and 5xx are worth retrying but must respect Retry-After.
  • Add a circuit breaker: after a run of failures, stop hammering and route traffic to an alternative source.

4. No reconciliation job

What happens. While things work, nobody compares your data to the supplier's. Discrepancies accumulate quietly: an order paid but no code stored; a code stored but the sale cancelled; a deposit debit with no successful delivery. It surfaces a month later, once logs have rotated and the supplier's claim window may have closed.

The fix. A daily automated reconciliation across three lists: your sales, supplier orders, deposit movements. The job should write mismatches to a dedicated table and raise an operator task, not simply log them. Separately, sweep for stragglers — orders that have sat outside a terminal state longer than they should.

5. Plaintext codes and codes in logs

What happens. Keys and PINs sit in the database in plaintext and leak into debug logs, APM traces and support email bodies. One database dump or one over-eager log aggregator, and the entire asset walks out.

Understand the stakes here: a digital code is a bearer instrument. You cannot un-redeem it and you cannot claw it back.

The fix.

  • Encrypt at rest under a dedicated key held in a secrets manager.
  • Hard-prohibit codes in logs, traces and error strings, with a redaction filter in the logger itself.
  • Restrict decryption rights to a minimal service list and audit every access.
  • Set a retention policy: delivered codes past the dispute window should not be kept indefinitely.

6. Ignoring partial fulfilment and stockout

What happens. Ten units ordered, seven delivered. The code treats the response as a boolean success/failure and either hands the customer seven codes while charging for ten, or marks the whole order failed and delivers nothing even though seven were already debited.

Stockout and revocation belong in the same bucket: both are normal states, not incidents.

The fix.

  • Model the order per line item — status and result live at the item level, not on the order as a whole.
  • Use explicit states: fulfilled, partially_fulfilled, out_of_stock, failed, revoked.
  • Define a policy for partial fulfilment: deliver what arrived, refund what did not, re-order the remainder from an alternative source.
  • On the storefront show an honest status instead of an error, and in the backend route to an alternative SKU or region as described in multi-source fulfilment routing.

7. No sandbox testing and no failure testing

What happens. The integration is validated with three successful orders and shipped. The first timeout, the first 429 and the first partially fulfilled order are met with live money and live customers.

The fix.

  • Use the supplier's test mode if one exists, and exercise more than the happy path: failure, timeout, partial fulfilment, insufficient deposit, unavailable SKU.
  • Test your own side with a mock that injects latency, dropped connections and duplicate events.
  • Explicitly test what happens during a deploy while orders are in flight — the single most underrated scenario.
  • The full set of states worth covering is laid out in order flow explained.

8. Hardcoded FX and assumed currency

What happens. The conversion rate is a constant in the code, or read once at application start. A month later you are selling below cost and cannot see why margin moved. The same mistake in another form: assuming API prices always arrive in one currency.

The fix.

  • Make the rate a configurable parameter with an explicit source, a refresh timestamp and a buffer for movement — not a constant.
  • Read the currency from the API response rather than assuming it.
  • Hold all monetary values as integers in minor units; never floats for money.
  • Log the rate applied to each order — without it, retrospective margin reconciliation is impossible.
  • How FX folds into your final cost is covered in how reseller discounts are calculated.

Pre-launch checklist

Check Done?
Local pending record written before the API call
Order-creation retry goes through an existing-order lookup
Event handler is idempotent and ignores stale events
Retries use exponential backoff, jitter and a circuit breaker
Daily reconciliation of sales, orders and deposit
Codes encrypted, absent from logs and traces
Partial fulfilment and stockout are explicit states
Failure paths tested, not just the happy path
FX configurable, currency read from the response

Where to source inventory

Half the problems on this list are decided by supplier choice rather than by code: a clear order-status model, honest states when stock runs short, and a catalogue broad enough that you are not maintaining five integrations with five different sets of bugs. FoxReload provides 900+ SKUs across keys, gift cards, top-ups, eSIM and software licences behind a single REST API with automatic delivery — one contract, one retry policy and one reconciliation job instead of a zoo.

The natural next reads are the API quickstart and the comparison of pre-purchased stock vs on-demand fulfilment, which determines how much of this list you have to implement at all.

Frequently asked questions

What should I do when an order-creation request times out?
Never blindly resend it. A timeout does not mean the order was not created — it means you do not know. The correct sequence is to write a local pending record before the call, then on timeout query the supplier's order list and try to match an existing order to that record by SKU, quantity and time window. Only create a new order if no match is found, and record the supplier order ID against your pending row either way.
Can I trust webhooks as the only source of order status?
No. Even when a supplier supports webhooks, delivery can be lost, duplicated or out of order — the network, your own deploy, a worker restart. Treat a webhook as a hint that says go and check, not as a fact. Your source of truth should be an explicit status read, and your handler must be idempotent and ignore any event describing a state older than the one you already recorded. Some suppliers provide no webhooks at all, in which case polling is the only mechanism you have.
How should delivered codes be stored securely?
Encrypted at rest under a dedicated key, with that key held in a secrets manager rather than in your repository or an environment variable sitting next to the app. Decryption rights should belong to as few services as possible, and every access should be audited. Codes must never reach logs, traces or error messages — including partial masking that still leaves them reconstructable. Also set a retention policy: a code already delivered and past its dispute window does not need to live with you forever.
Why do I need a separate reconciliation job if statuses already arrive?
Because discrepancies arise precisely where both sides believe everything is fine. A daily job matches three lists — your sales, supplier orders and deposit movements — and catches orders stuck in an intermediate state, debits with no delivered code, and delivered codes with no corresponding sale. Without it you learn about problems from a customer or from a month-end report, by which time logs may have rotated and the supplier's claim window may have closed.
See FoxReload wholesale prices

Related articles