Combatting Credit Card Fraud

25th August 2024

In the world of online business; if you build it, they will not come. It takes work to get your name out there and unfortunately you cannot attract good customers without also attracting unwanted attention. Run a website that allows customers to purchase or subscribe using a credit card and you're likely to end up battling credit card fraud. Whether it's card testers looking to validate credit card numbers, or someone that wants to use your service but doesn't want to pay for it, we need to do what we can to prevent it.

Futurama's Fry holding money up shouting - shut up and take my money! An exceedingly rare form of customer. If you know where they hang out, please let me know.
'Defense in depth' means this isn't a problem with a single solution, we need to build layers of security around our application to reduce the chances of fraudulent transactions getting through. Let's look at some of the layers and techniques we could implement, as well as how to identify potentially fraudulent transactions.

Rate Limiting

Unless you're selling tickets for Taylor Swift's next tour, you're unlikely to have legitimate customers trying to check out 35 times a second.

Rate limiting can be performed in a few places, but the further out from our application we can push it, the less impact it will have on the rest of our infrastructure. Many cloud providers offer hosted firewalls that will allow you to limit the rate of requests to specific routes or endpoints of your application. Any requests blocked at this point do not have to be handled by our server and shouldn't have any impact on other customers. One bonus of this is that they can help protect you against a variety of malicious requests like SQL injection.

If your cloud provider doesn't offer a hosted firewall, or the available rate-limiting options aren't appropriate, we can rate limit at the Nginx or Apache level. Our servers will still have to handle these requests, but they'll get filtered before they reach the application.

Finally, we can perform rate limiting within our application code. If a high rate of requests reaches this point it could impact performance for other users, but it allows us more flexibility with rate limiting based on user sessions or other variables.

Focus on the most likely endpoints bad actors are going to target; login and registration for credential stuffing and purchase/checkout endpoints for card testing. Measuring the 'normal' rate of requests to these endpoints for your business will help you determine a rate at which to start limiting.

Captcha

When the day comes that robots walk among us, I hope they don't take offence at all the sites requiring them to “prove you're human” before they can sign up or purchase.

A screenshot of a difficult captcha with someone sweating trying to solve it Am I even human? Better steer clear of Harrison Ford just in-case...

When you have a list with thousands or millions of username/password combinations or credit card numbers, the speed at which you can check them is critical. The rate limiting we've already introduced should reduce this significantly, but our villains are still going to perform their testing using a script rather than by hand. The popularity of World of Warcraft may have demonstrated humanity's ability to put up with grinding, but entering billing details and card numbers into a checkout form will drain even the most determined fraudster's will to live. Enter Captcha.

Adding some implementation of Captcha to your vital endpoints is relatively simple, but it does come with trade-offs. Set the bar too high and you'll frustrate legitimate customers, too low and it won't stop bad actors. Unfortunately it's also fairly ineffective. A determined foe could automate the rest of the flow and spend their day completing Captchas, or paying someone else to do it for them. There are even automated tools that offer to complete them for you. As long as we don't rely on it too heavily though, it could still help us filter out some fraudulent requests and rate-limit things even further.

Next, let's start looking at some of the characteristics of requests that could help us highlight the bad apples.

Spot the Differences

Location, location, location. We all love to visit new and exotic locations, and what better way to spend your time in paradise than sipping Mai Tais by the pool and signing up for that cool new SaaS you read about on Hacker News? Last time I checked, the number of countries you can visit in a day is still limited by the laws of physics and the credit your bank thinks you're good for. Of course, when online we can seemingly change our location using a VPN, but how many different VPNs in how many different countries is reasonable? If the same user tries to check out from America one minute, Cyprus the next and then Singapore five minutes later, how likely is it they're just an honest customer struggling to type their credit card number in correctly rather than someone trying to bypass your rate limits?

Pick a card, any card. According to CapitalOne, the average American has three credit cards and money.co.uk estimated that in 2019 the average Brit had 1.7 credit cards. Not everybody is average, but if you have a single user attempting to check out using 15 different card numbers, you can be fairly confident that not all those cards belong to them.

As well as billing details supplied by the user, each request we receive includes headers that give us additional information about the device and software the customer is using. These headers are controlled by the client, but that can turn out to be helpful. Assuming you're running a regular web application, you wouldn't expect many people to be browsing your site using HeadlessChrome. If our user allows their tools to set the headers, or even sets them at random, you may see requests from combinations of device and operating system that make no sense, like Windows running on an iPad.

Even if the headers seem reasonable, a high number of different device types could also be a red flag. If their first checkout request comes from an iPhone, the next from a Windows machine, then a Samsung device, are they trying it on every device they have in the house, or trying to avoid detection?

Spot the Similarities

Unless your advertising is hyper-targeted, your customers are likely to be dotted all over the country, if not the world. If 15 different users try to check out and all claim to live on the same street, it should raise alarms as well as eyebrows.

These days there are hundreds, if not thousands, of free e-mail providers like Hotmail and Gmail. The barrier to creating a new account on these sites is so low that fraudsters can churn them out at such a rate that you've got no hope of tying them together. Are [email protected] and [email protected] the same person, or just distant cousins? However; given a private domain that doesn't allow you to register for an email address, we know that any email addresses are created by someone with some control over that domain. In this situation, you can be more confident that [email protected] and [email protected] are related. If you get too many checkout requests from users on a private domain, using different credit card numbers, your suspicions should be raised.

As a counter-point to the single user attempting multiple cards, we could have multiple users attempting the same card. If their e-mail addresses indicate a private domain then it could be someone trying to help out a colleague, but if they're GMail addresses, then we may pay closer attention.

Check Out the High Roller!

People testing credit cards want to fly under the radar. They want to validate whether a card number works, without the legitimate owner of the card being alerted. The smaller the transaction they create, the less likely it is to get spotted. This means that testers are likely to aim for your cheapest product or smallest subscription.

This brings us to coupon codes. It may seem counter-intuitive that someone using a credit card that doesn't belong to them would bother to use a coupon code, but it comes back to making the transaction as small as possible, reducing the chances of it getting flagged. Coupon codes are shared widely on the internet, so don't assume the fact they're using a coupon means they're a legitimate customer.

Even better than coupons are free trials that require you to provide a card number but don't immediately charge the card. When using the card to create the trial, the card will be validated. If this is successful, it gives the tester a reasonable level of confidence that the card is working.

Lights, Camera, Reaction?

In most situations we want to give users helpful feedback. If postal code validation fails we need to highlight it for the user and telling them “something went wrong” isn't good enough. If we're confident the request is fraudulent, the opposite becomes true; we want to be as un-helpful as possible.

You've put all your checks in place and somebody tries to check out on your site. The system flags it and you're confident it's a fraudulent transaction, so what do you do? One option is to block the transaction and disable the user account, but if the tester notices they get logged out, it serves as a strong indicator that they need to change their tactics. When this happens, they can just register a new user account and try again, adjusting some of the details they're using to try and work out what it was that got their original attempt flagged. Instead, preventing the transaction, but giving a different response could benefit us in the long run. Does a 500 response mean you're wise to their tricks, or just that your app is a bit flaky and they should try the same details again?

AI

Are you even allowed to publish a technology post these days without mentioning AI?

All the techniques mentioned so far are what you might call “traditional” engineering implementations, but preventing credit card fraud is an area where AI is likely to have an increasing impact in the future. If anyone loves pattern matching it's those adorable AIs. One thing we've learned from ChatGPT and other large language models is “the more you put in, the more you get out”. They are only effective because of the vast quantities of training data that has been pushed through them.

Payment providers like Stripe and Braintree process huge numbers of transactions every single day and you can be sure they're using that data to train models of their own. Thankfully, they allow us to leverage what they're learning to reduce fraud further, so make the most of it. Enabling fraud protection and similar options on your chosen payment provider will help you reject transactions that have passed our other checks.

Get Off My Lawn!

Even with all the layers of protection we've implemented, no solution is perfect and there's still a chance that fraudulent transactions will sneak through. Our aim should be to frustrate them enough that they take their “business” elsewhere.

Hopefully this article has given you some ideas of ways you can combat credit card fraud on your site. If you're doing something different that you think I've missed I'd love to hear about it so please get in touch! Most of all; if this is a problem you're dealing with - good luck!