How a Pocket transfer settles in seconds
Most Pocket transfers arrive in seconds, in 160 countries. A look at the double-entry ledger, pre-funded local accounts and idempotency keys that make that fast and keep every cent accounted for.

Jun Seo
Staff Engineer, Payments
Share

Photo:
Last month, 71% of Pocket transfers to another country arrived in under 20 seconds, and transfers between two Pocket accounts took a median of 400 milliseconds. People notice speed first. What they never see, and what took us most of the work, is making sure that every one of those transfers is recorded exactly once, balances to the cent, and can be retried safely when a phone loses signal halfway through.
This is a tour of how a transfer moves through Pocket, from the tap in the app to the money landing.
Not crossing borders
The fastest way to move money across a border is not to. A traditional international transfer hops from your bank through one or more correspondent banks to the recipient's bank, and each hop adds a day and sometimes a fee.
Instead, Pocket keeps accounts with partner banks in the countries we send to, funded in the local currency. When you send euros to someone in Mexico, the euros leave your balance and land in our euro pool, and pesos leave our Mexican account through the local instant payment system to the recipient. Nothing crosses a border at that moment. We rebalance those pools in bulk later, at the mid-market rate, with large and predictable transfers between our own accounts.
That is why speed depends on the destination's local systems. Where they are instant, like in the eurozone, the UK, Brazil, India and Mexico, so are we. Where they are not, the app tells you the expected arrival time before you confirm.
A ledger that always balances
Every balance in Pocket is the sum of entries in a double-entry ledger. Nothing updates a balance directly. A movement of money is written as a set of entries that, within each currency, add up to zero.
Here are the entries for sending €500 to a US dollar account, with a fee of €2.35 and the mid-market rate at 1.0842:
Account | Currency | Entry |
|---|---|---|
Customer balance | EUR | −500.00 |
Fee revenue | EUR | +2.35 |
FX pool | EUR | +497.65 |
FX pool | USD | −539.55 |
Outbound payments, US | USD | +539.55 |
The euro entries sum to zero, and so do the dollar ones. If they ever did not, the write would be rejected. That one rule catches whole classes of bugs before they become someone's missing money, and it means any balance at any moment can be rebuilt by replaying the entries behind it.
Entries are never edited or deleted. If a payment is returned by the receiving bank, we write new entries that reverse it, so the history shows both what happened and how it was undone.
Idempotency keys
Mobile networks are unreliable. You tap Send on a train, the request leaves your phone, the tunnel arrives, and the app never hears back. Did the transfer go through? The app does not know, so it tries again. Without care, that retry sends the money twice.
Every request that moves money carries an idempotency key, a unique ID the client generates once per intended transfer. The app, our public API and our internal services all follow the same rule:
The first time we see a key, we store it with a fingerprint of the request body in the same database transaction that writes the ledger entries. A retry with the same key and the same body returns the original result, including the original transfer ID, without doing anything again. A retry with the same key and a different body is rejected, because it almost always means a bug in the client.
Keys are kept for 30 days. Developers using our API get exactly the same behaviour, which matters a lot when a payroll job crashes halfway through a batch of 400 payouts and is restarted.
From hold to settled
A transfer moves through a small number of states, and each change writes ledger entries:
Held. The amount and fee leave your available balance and sit in a hold. This happens before anything else, so the money cannot be spent twice.
Sent. The payout instruction goes to the local payment system. Most instant systems confirm within a few seconds.
Settled. The receiving bank confirms the credit. The hold is released into the entries above, and you get a notification.
Returned. Rarely, a receiving bank sends the money back, usually because an account is closed. We reverse the entries and refund you in full, fee included.
Checking our own work
Fast systems can fail fast too, so we check the ledger against reality continuously. Every partner bank account we hold has a statement feed, and a reconciliation service matches each line against our ledger within minutes. Anything unmatched after an hour pages an engineer on call.
Last quarter we processed a little over 210 million ledger entries, and reconciliation flagged 63 items for a human to look at. Every one was explained, mostly by partner banks posting late, and none involved a customer's money going missing. You can read more about how we protect accounts on our security page.
#ledger
#payments
#infrastructure

Written by
Jun Seo
Staff Engineer, Payments




