Revolut $20M loss – my theory

Many people have been speculating lately about the nature of Revolut’s $20M loss, so here is the theory I find most probable: this is most likely caused by mishandling authorizations that exceed the processing time.


When a wallet receives an authorization request, it is allocated a certain time span to respond with either “approve” or “decline.” The problem arises when the wallet fails to respond to the payment processor within that time. Some payment processors will do nothing (i.e., continue to wait for the wallet, allowing the transaction to time out within the interchange network), while others will preemptively reject the transaction and send the same transaction data to the wallet but with a rejection status code already set (or send advice).

My theory

I strongly suspect that what happened here is Revolut’s wallet taking a long time during transaction processing, while the upstream processor sent a duplicate transaction (or advice) with rejection to it. The wallet was built to always create a refund on that transaction, assuming that the lengthy processing authorization would eventually be approved.

How was it exploited?

A more interesting speculation is how this came to be exploited. First, for someone to notice this, a relatively large percentage of transactions needs to time out (which doesn’t sound too probable to me, but then again, I have never used Revolut). If many transactions were timing out, someone could have observed that even transactions exceeding the wallet balance could time out (e.g., if there is $100 in the wallet, a user tries a $200 transaction, and if it fails, the wallet balance remains at $100, but if this timeout mechanism kicks in, the wallet balance would be $300 – that’s when they would rush to an ATM to withdraw cash before the balance could be manually adjusted).

The second possibility is that this was caused by a feature often offered by wallets – linking an external card to the wallet with the purpose of auto-loading the wallet. A malicious user would have a low wallet balance and the wallet linked to their own external card (which is empty or has a low balance). Now, upon authorization attempt, the wallet would try to perform an auto-load, which increases the probability of triggering a processing timeout. If a timeout occurs, it could be exploited as explained above. Imagine having two cards issued by the same auto-loading supported wallet and linking them together…

How to mitigate?

The most obvious (but not really foolproof) solution would be to try to make your wallet faster. Unfortunately, due to the nature of transaction data, this is not always easy. One thing that always helps in making a wallet faster is to make it simpler. One way to simplify a wallet is to remove the code for integrating the payment processor from it – that is what we did with our service.

Why speeding up the wallet is not a truly foolproof solution: because there are network communication elements that you cannot influence and that are above you in the chain. For example, even if your payment processor thinks it has 2.3 seconds to respond to a transaction, the transaction might have already timed out when it reached the payment processor – maybe the payment processor’s connection to the interchange network is unstable.

The only real solution is to actually build your wallet to handle these situations properly when they occur. This would mean that the authorization process can be aborted, or that the handler for the second message first checks the outcome of the first message (so it doesn’t blindly reverse all, but rather reverses only approved transactions). If the issuer has configured SIP (stand-in processing) for cards, then the entire situation can become even more complicated – on one hand, you can have the payment processor sending its own reversal transaction, but behind it, the SIP server might be sending another one (or, for example, the payment processor could reverse, but the SIP could accept it, in which case the transaction chain might look like this: wallet approval, payment processor reversal advice, SIP approval advice).

Obviously, it would be great if you could somehow simulate and test these scenarios. Our Nautilus supports simulating transactions with approving advice (so it would create an authorization request followed by an approved authorization advice regardless of the authorization request response), but unfortunately, it doesn’t support this scenario with an authorization request followed by reject advice – this real-life situation inspired us to add that ability to the Nautilus development backlog as well.





Leave a Reply