Wallet transfers with API providers: keep request queues clear
How chained transfer calls between a platform and its API providers flood request queues, and how a queue, fast replies and pushed results keep the platform stable when a provider is slow.
A platform holds its users' wallets; API providers run the servers behind the services the platform offers. Before a user can use a provider's service, funds move from the platform to the provider, and when the user is done they move back. Most integrations build these transfers as a chain of synchronous HTTP calls in both directions. Under load the chain fills request queues on both sides, and one slow provider can take the whole platform down with it. Your platform's stability should not depend on someone else's servers.
A transfer as a chain of requests
The platform's worker is held for the whole round trip, including the provider's call back into the platform. The provider's worker is held while it waits for that callback. Each side waits on the other, so when traffic rises both pools fill at the same time. A timeout leaves the transfer's status unknown, which brings status polls and retries, and without idempotency keys a retry can move the money twice.
How one slow provider becomes everyone's problem
A worked example, not a benchmark (written 2026-11-09): 500 transfers a second spread over three providers, and a pool of 200 workers on the platform. Little's law gives the workers in use: transfers per second × seconds each one holds a worker.
| Chained HTTP | Queue, accept and push | |
|---|---|---|
| Time a user's request holds a platform worker | 20 ms + a 150 ms provider call = 170 ms | 20 ms |
| Platform workers busy at 500 transfers a second | 500 × 0.17 s = 85 | 500 × 0.02 s = 10 |
| One of the three providers hangs until a 5 s timeout | about 900: the pool of 200 is used up and every provider fails | still 10: only that provider's transfers wait |
| Requests while waiting for a result | a status poll every 2 s for each pending transfer | none, plus one batch check per provider every few minutes |
After: accept fast, settle in the background, push the result
- The platform answers the user first. It writes the transfer to its ledger as pending, puts it in a queue and replies. The user's page shows "pending" and updates by itself when the event arrives.
- A dispatcher sends transfers to each provider with an idempotency key, a short timeout and a fixed number of slots per provider. The provider replies 202 as soon as it has recorded the transfer, so a retry with the same key is always safe.
- No callbacks. The request carries everything the provider needs, signed, so the provider does not call the platform back to verify it.
- The result is pushed. The provider publishes
transfer.settledto a private channel for that platform, such asprivate-partner-acme. The platform's backend keeps one WebSocket connection and subscribes, authorized by the provider's auth endpoint. It updates the ledger and publishesbalance.updatedto the user's wallet channel, as in balance sync between products. - One check for anything missed. Every few minutes the platform asks each provider for the transfers still pending, in one batch request, instead of polling each transfer.
Keeping the platform stable when a provider is not
- Separate slots for each provider. A provider that slows down fills only its own slots in the dispatcher. Logins, deposits and every other provider keep their workers.
- Stop sending to a provider that stops answering. After a run of timeouts, pause that provider's queue and show its services as unavailable, then try again after a short wait. Transfers stay pending in your ledger, not in held connections.
- A dropped WebSocket connection blocks nothing. Transfers keep being accepted. When the connection is back, the batch check settles whatever happened in between.
- Your users never wait on a provider's response time. The page answers in the platform's own time, and the result appears when it is ready.
Common questions
Why does a slow API provider slow down the whole platform?
Because the platform's workers wait for it. Each synchronous call holds a worker for as long as the provider takes, and the workers are shared with everything else. When one provider slows down, those workers run out, and requests that never touch that provider queue behind it.
Does WebSocket replace the transfer API?
No. The transfer itself stays an HTTP request with an idempotency key, recorded in both ledgers. WebSocket replaces the waiting: the held connection, the status polls and the callbacks. The result arrives as an event instead.
What if the platform is disconnected when a transfer settles?
Nothing is lost, because both ledgers already hold the transfer. pusher-js and the other client libraries reconnect on their own, and the batch check of pending transfers settles anything that was missed. Use the transfer ID to ignore an event you have already applied.
How many connections does this need?
One WebSocket connection from the platform's backend to each provider's channel is enough for every transfer with that provider, however many users it serves. On Hibira each publish counts as one message; on paid plans deliveries to subscribers are not counted. See pricing.
Product names are trademarks of their owners.