While increased sales are good for your business, customers acting in bad faith can dry up available promotional campaign dollars by repeatedly using coupon codes. Preventing customers, either signed-in or guests, from abusing your coupons and promotions can help:
Install a browser identification service
Device identification services like Fingerprint Pro can accurately identify anonymous visitor traffic by uniquely identifying their browsers. With JavaScript running in the background, Fingerprint Pro generates a unique identifier (the visitorId
) anytime someone performs a critical action, such as checkout. Following the best practice of never trusting data from the client side, we can leverage server-side tools to validate incoming fingerprints. As a result, you can protect your storefront’s coupon system against fraudsters without harming the checkout experience for friendly customers.
Configuring Fingerprint Pro for Coupon Abuse Prevention
Here is boilerplate logic for protecting coupon codes, but additional checks may be necessary depending on your business needs.
// Initialize the agent
const fpPromise = import('https://fpjscdn.net/v3/your-public-api-key')
.then(FingerprintJS => FingerprintJS.load({
endpoint: 'https://fp.yourdomain.com'
}));
// Once you need result, get and store it.
// Typically on page load or on button click.
fpPromise
.then(fp => fp.get())
.then(fpResult => {result = fpResult})
endpoint
property here is highly recommended for all customers. It ensures the Fingerprint API request is not blocked by browser extensions and provides the highest accuracy for browsers like Safari and Firefox.visitorId
. For subsequent checkouts when a customer attempt a coupon, verify the visitorId
has not used this coupon code in the past.const couponData = {
visitorId,
requestId,
couponId
};
const response = await fetch(`/api/checkout/coupons}`, {
method: 'POST',
body: JSON.stringify(fingerprintData),
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
The next steps should be performed on the back-end. Please see our GitHub for available server-side integrations.
visitorId
and requestId
for valid formatting. We can use regex before sending a potentially unnecessary Fingerprint server API request.const visitorId = req.body.visitorId;
const requestId = req.body.requestId;
const isRequestIdFormatValid = /^\d{13}\.[a-zA-Z0-9]{6}$/.test(requestId);
const isVisitorIdFormatValid = /^[a-zA-Z0-9]{20}$/.test(visitorId);
if (!isRequestIdFormatValid || !isVisitorIdFormatValid) {
return getForbiddenReponse();
}
const fingerprintJSProServerApiUrl = new URL(
`https://api.fpjs.io/visitors/${visitorId}`
);
fingerprintJSProServerApiUrl.searchParams.append('request_id', requestId);
const visitorServerApiResponse = await fetch(
fingerprintJSProServerApiUrl.href, { method: 'GET', headers: { 'Auth-API-Key': 'secret-api-key' } }
);
// If there's something wrong with provided data, Server API might return non 200 response.
// We consider these data unreliable.
if (visitorServerApiResponse.status !== 200) {
reportSuspiciousActivity(req);
// Handle error internaly, do not allow the coupon code to be applied.
}
const visitorData = await visitorServerApiResponse.json();
return visitorData;
if (visitorData.error || visitorData.visits.length !== 1) {
// Do not allow the coupon code to be applied.
return getForbiddenReponse();
}
if (new Date().getTime() - visitorData.visits[0].timestamp > 3000) {
// Do not allow the coupon code to be applied.
return getForbiddenReponse();
}
// This is an example of obtaining the client IP address.
// In most cases, it's a good idea to look for the right-most external IP address in the list to prevent spoofing.
if (
request.headers['x-forwarded-for'].split(',')[0] !==
visitorData.visits[0].ip
) {
// Do not allow the coupon code to be applied.
return getForbiddenReponse();
}
const ourOrigins = [
'https://example.com',
];
const visitorDataOrigin = new URL(visitorData.visits[0].url).origin;
if (
(visitorDataOrigin !== request.headers['origin'] ||
!ourOrigins.includes(visitorDataOrigin) ||
!ourOrigins.includes(request.headers['origin']))
) {
// Do not allow the coupon code to be applied.
return getForbiddenReponse();
}
/**
* Checks if a coupon exists with the given coupon code.
*/
export async function checkCoupon(code) {
return await CouponCode.findOne({
where: {
code: {
[Op.eq]: code,
},
},
});
}
async function getVisitorClaim(visitorId, couponCode) {
return await CouponClaim.findOne({
where: { visitorId, couponCode },
});
}
const coupon = await checkCoupon(couponCode);
// Check if the coupon exists.
if (!coupon) {
return getForbiddenReponse(res, 'Provided coupon code does not exist.', 'error');
}
const wasCouponClaimedByVisitor = await getVisitorClaim(visitorId, couponCode);
async function checkVisitorClaimedRecently(visitorId) {
const oneHourBefore = new Date();
oneHourBefore.setHours(oneHourBefore.getHours() - 1);
return await CouponClaim.findOne({
where: {
visitorId,
timestamp: {
[Op.between]: [oneHourBefore, new Date()],
},
},
});
}
/**
* Claim coupon on behalf of the visitor.
*/
export async function claimCoupon(visitorId, couponCode) {
const claim = await CouponClaim.create({
couponCode,
visitorId,
timestamp: new Date(),
});
await claim.save();
return claim;
}
const visitorClaimedAnotherCouponRecently = await checkVisitorClaimedRecently(visitorId);
if (visitorClaimedAnotherCouponRecently) {
return getForbiddenReponse(res, 'The visitor claimed another coupon recently.\n', 'error');
}
await claimCoupon(visitorId, couponCode);
return getOkReponse(res, `Coupon claimed you get a 119 USD discount!`, 'success');
We have built a fully open-sourced coupon abuse prevention demo to demonstrate the concepts above. Feel free to view the live demo on StackBlitz or jump into the GitHub repo for code. If you have any questions, please reach out to us at support@fingerprint.com.
Fingerprint’ open source technology is supported by contributing developers across the globe. Stay up to date on our latest technical use cases, integrations and updates.