In short and in plain language
A discount code is like a small secret with money value. People who do not know it can try to guess it. Programs can try a huge number of codes.
In weeks with big sales, this is a special risk. Then many codes are in use.
The article shows what helps. Codes should be hard to guess. The voucher field slows down too many attempts. Many wrong attempts trigger an alert.
We review the attempts at the voucher field in your shop. Before a big sale, we make the alert stricter.
A discount code is a small secret with a cash value. Whoever knows it gets a reduction – and whoever does not can guess it, provided the shop lets them. The OWASP project on automated threats lists exactly this pattern as a threat of its own, OAT-002 Token Cracking: the mass enumeration of coupon numbers, discount codes and similar tokens (OWASP Automated Threats). In Black Week this threat meets the period in which most codes are valid at the same time. For Black Friday and Cyber Monday 2025 the German Retail Federation expected revenue of 5.8 billion euros (HDE), and 52 percent of internet users aged 16 and over in Germany intended to take advantage of the reduced prices (Bitkom). In 2026 Black Friday falls on 27 November. From mid-October that leaves a good six weeks to set up code length, rate limiting, alerting and the end of the promotion so that a bot at the coupon field gets noticed before it skims off margin. This article shows the arithmetic behind robust codes, the places where shops typically need to add protection, and a schedule up to sale week.
Key takeaways
- OWASP lists the mass enumeration of coupon and discount codes as a threat of its own, OAT-002 (OWASP Automated Threats). The more valid codes are in circulation, the faster a bot hits one of them.
- The sample pattern in the Shopware documentation with 3 random letters and 2 random digits yields 1,757,600 codes; with 10,000 issued, every 176th guess is a hit on average (own calculation based on Shopware documentation).
- The coupon field needs its own rate limit per cart, customer account and IP network. The default limiter for cart line items counts per code and IP address via the Store API, and not at all at the storefront coupon field (Shopware source code).
- Failed attempts at the coupon field belong in monitoring as a metric of their own: a threshold derived from previous weeks, tighter settings and extended on-call cover for the week around Black Friday on 27 November 2026.
- In Shopware, new-customer discounts are tied to the customer account, not to the person (Shopware documentation). Normalised email address, postal address and payment method reveal multiple accounts without locking out genuine new customers.
- Individual codes can be redeemed only once (Shopware documentation). Fixed codes need total and per-customer limits; at the end of the promotion, validity, deactivation and redemptions are checked.
Why discount codes are a target of their own
A coupon code works like a password without a username. It unlocks a benefit that converts into money, and the shop checks with every entry whether it is valid. For login secrets, NIST limits consecutive failed attempts per account to no more than 100 and then disables the authenticator (NIST SP 800-63B). At the coupon field there is no such account: nothing gets locked after too many failed attempts, and there is no owner who receives a warning email. Someone guessing there initially bothers nobody, and the shop’s response tells them with every attempt whether they were right.
Mass enumeration of coupon numbers, voucher codes, discount tokens, etc.
The OWASP project maps this pattern to the weaknesses CWE-799 and CWE-837 (OWASP Automated Threats). The first describes an application that does not sufficiently limit the number or frequency of interactions with an actor (MITRE CWE). The second describes an application that fails to enforce, or improperly enforces, an action that may only be performed once (MITRE CWE). Both apply to coupons: a frequency brake counters guessing, and a hard single-use check counters repeated redemption.
The CAPEC attack catalogue describes the core of the arithmetic in entry CAPEC-112: what matters is how quickly an attacker can explore the space of possible secrets (MITRE CAPEC). The defender has to make sure that the time and resources needed for this exceed the value of what can be gained (MITRE CAPEC). For discount codes this means: the character space has to be large enough, attempts have to be slowed down, and the shop has to notice when someone searches anyway. In Black Week these three points carry more weight than usual. Many promotions run in parallel, codes are distributed via newsletters, partners and social channels, and an unusual curve easily gets lost in the general rise in load.
Four patterns of code abuse
During a promotion, shops encounter four forms of code abuse. They differ in technique but have one thing in common: each individual order looks unremarkable on its own. They only become visible in aggregate, in counters and curves.
Enumeration
A script sends codes following a recognised pattern to the coupon field or the cart interface. The target is valid individual codes from large series, for example from newsletter or partner promotions.
Repeated redemption
A code meant to apply once is applied several times: across several carts, through parallel requests or by stepping back in the checkout. The cause lies in the validation logic, not in the code.
Multiple accounts
A new-customer discount is collected through many freshly created accounts. Each account is formally new, but behind them are the same person, the same address or the same script.
Leaked codes
A code for a closed audience appears on coupon sites or in forums and is redeemed by everyone. Technically this is not an attack, economically it has the same effect.
Distinguishing related attacks helps to assign alerts correctly. In card testing at the checkout bots check card data, not codes; account takeover is about other people’s customer accounts. The tools are similar, the metrics are not: for code abuse, what counts is how often the coupon field is called with invalid values and how redeemed codes are distributed across accounts, addresses and origins.
Code length and character space: the arithmetic before the sale
How much protection a code offers by itself can be calculated before the promotion. Shopware generates individual promotion codes either with a fixed length or according to a custom pattern in which %s stands for a random letter from A to Z and %d for a random digit from 0 to 9 (Shopware documentation). The sample pattern in the documentation, SW-%s%d%s%d%s-TEST, contains five random positions: three letters and two digits. That gives 1,757,600 possible codes (own calculation based on Shopware documentation).
A second figure is decisive: how many of them are valid. An attacker is not looking for one particular code but for any of those issued. With 10,000 codes from a newsletter promotion, on average every 176th random guess is a hit (own calculation based on Shopware documentation). The table shows how this shifts with length – as our own calculation with 10,000 valid codes and an assumed 1,000 attempts per second without throttling.
| Code structure | Possible codes | Guesses per hit with 10,000 valid codes | Time to a hit at 1,000 attempts per second |
|---|---|---|---|
| Sample pattern SW-%s%d%s%d%s-TEST: 3 letters, 2 digits (Shopware documentation) | 1,757,600 | about 176 | under 1 second |
| 8 characters from a set of 32 | about 1.1 trillion | about 110 million | about 31 hours |
| 10 characters from a set of 32 | about 1.1 quadrillion | about 113 billion | about 3.6 years |
| 12 characters from a set of 32 | about 1.2 quintillion | about 115 trillion | about 3,650 years |
Two conclusions follow. First, a short pattern must not turn into a series of thousands of codes without additional random positions: every additional code issued makes guessing easier. Second, even twelve characters do not replace throttling. A code of 12 characters from a 32-character set carries 60 bits of randomness. For one-time codes from OTP generators such as authenticator apps, NIST requires a rate limit on failed attempts below 64 bits (NIST SP 800-63B). A coupon code is not a login, but the logic still applies – especially since the attacker, not the shop, decides how many attempts are made per second.
A character set without look-alikes
A rate limit right at the coupon field
Since version 6.4.6.0 Shopware has shipped with rate limits which, according to the developer documentation, reduce the risk of brute-force attacks on pages such as login or password reset (Shopware developer documentation). Customer login is limited in stages: 10 requests in 10 seconds, 15 in 30 seconds and 20 in 60 seconds (Shopware developer documentation). The list of default limiters contains no entry of its own for promotion codes.
The closest is the limiter for cart line items, because a promotion code is kept in the cart as a line item. For requests via the Store API its counter key is made up of the line item’s identifier and the IP address, and for a promotion code that identifier is the entered code itself (Shopware source code). Anyone who makes each attempt with a different code therefore starts with a fresh counter every time. Via the storefront coupon field it does not count at all: the cart service passes on a request without an IP address there, and without an address the route skips the check (Shopware source code). This limiter is not built for enumeration, so a dedicated brake at the coupon field belongs in the shop.
In its IT-Grundschutz, the German Federal Office for Information Security (BSI) explicitly requires protection of web applications against unauthorised automated use (BSI IT-Grundschutz) and at the same time requires that the effect of these protections on legitimate users is taken into account. For the coupon field this means a brake with several counters, each capturing something different:
- Per cart or session: a few failed attempts in quick succession, then a growing waiting time. Genuine customers mistype once or twice, not twenty times in a minute.
- Per customer account: the same limit across several sessions, so that a new cart does not reset the counter.
- Per IP address and network: a more generous limit, because many genuine customers can share a company or mobile network address.
- Across the whole shop: a counter of all failed attempts that does not throttle but alerts. It detects distributed attacks that stay under every individual limit.
- Uniform response: the message for an unknown code should not differ from the one for a code that has already been redeemed. Every distinction reveals that a code exists.
The last point follows a principle the same BSI module describes using accounts as the example: if an application reveals information about existing accounts, a list of valid accounts can be built from it (BSI IT-Grundschutz). The same applies to codes. Once the limit is reached, the shop responds with status code 429, which according to the RFC indicates that too many requests were sent in a given amount of time (RFC 6585). The response may carry a Retry-After header with the waiting time. The RFC also notes that a server under attack does not have to answer every request with a 429, because responding itself consumes resources (RFC 6585). For mass attempts from one network, a rule in the web application firewall is therefore the cheaper place.
Detecting and reporting failure spikes
A brake without measurement stays silent: it kicks in, but nobody finds out. Failed attempts at the coupon field therefore belong in monitoring as a metric of their own, broken down by minute, sales channel and origin. The shop provides the baseline itself. In quiet weeks the number of invalid code entries moves within a narrow band, on sale days it rises with visitor numbers. The alert therefore does not compare against a fixed number but against the ratio of failed attempts to carts.
As a countermeasure against automated access to sensitive business flows, the OWASP API Security Top 10 name the search for non-human patterns, for example when less than one second passes between “add to cart” and “complete purchase” (OWASP API Security Top 10). At the coupon field such patterns look like this: attempts milliseconds apart, codes in alphabetical or numerical order, many entries without a single change of items, and requests without a preceding page view. The web server access log already gives a first view. The storefront accepts codes via the route POST /checkout/promotion/add (Shopware source code):
#!/usr/bin/env bash
# Attempts at the coupon field per IP address and minute from the access log
# ("combined" format, storefront route POST /checkout/promotion/add)
LOG="${1:-/var/log/nginx/access.log}"
THRESHOLD=20 # attempts per minute and address before a line is printed
awk -v s="$THRESHOLD" '$6 == "\"POST" && $7 ~ /^\/checkout\/promotion\/add/ {
split($4, t, ":"); minute = t[2] ":" t[3]
n[$1 " " minute]++
}
END { for (k in n) if (n[k] >= s) print n[k], k }' "$LOG" | sort -rn | head -20
The access log shows attempts, but not whether a code was valid. That is enough for a first alert: someone who tries twenty codes in one minute is not shopping in the usual way. Only the application knows the outcome of each entry. A dedicated log entry belongs there that records time, cart, account and result, but not the full code. Truncated to the first few characters, it remains useful for pattern analysis without exposing a valid code in the log.
The threshold comes from your own history
How thresholds, escalation and alert fatigue interact is described in the article on log monitoring and alerting. For the coupon field, one level for the daily report and one for immediate notification are enough to start with.
Protecting new-customer discounts against multiple accounts
The new-customer discount is the easiest to collect several times, because it is tied to a property that can be manufactured. In Shopware, the “New customer” condition in the Rule Builder checks whether the date of the first login equals today’s date; the documentation announces its removal and recommends the “Time since first login” condition instead (Shopware documentation). Both conditions are tied to the customer account. A new account with a new email address is a new customer as far as the rule is concerned.
The OWASP API Security Top 10 describe the pattern using a referral programme as an example: an attacker automates registration, and every new account credits them with a bonus (OWASP API Security Top 10). For a shop the economic effect is the same when a welcome code flows through many accounts into orders shipped to the same address. At the same time, discounts are a trade for data that many customers enter into deliberately: 56 percent of online shoppers are willing to provide their data in exchange for discounts (Bitkom). The new-customer discount therefore remains a useful tool. It just needs a check that reaches beyond the account and works with signals rather than a single hard criterion:
- Normalised email address: many mailboxes accept additions after a separator such as “+”. RFC 5233 describes this subaddressing and points out that the encoding is site-specific (RFC 5233). In Gmail addresses dots do not matter, in Gmail mailboxes on an organisation’s own domain they do (Gmail Help). The normalised form is only used for comparison and does not replace the stored address.
- Shipping and billing address: the same address in several new-customer orders during the promotion, even with differently written street names or house numbers.
- Payment method: the same payment account behind several new customer accounts, where the payment provider supplies a recurring identifier for it.
- Proximity in time: many registrations followed by code redemption in quick succession from the same network.
A hit on one of these signals is a case for review, not proof. Families share addresses, colleagues share a company network. A tiered approach makes sense: the order is accepted, the discount is reviewed before shipping when accumulations look suspicious, and only clear series lead to action under the published promotion terms. Those terms state before the start that the discount applies once per person or household – without that, any later decision lacks a basis.
Data protection in abuse checks
When codes end up on coupon sites
Not every abuse needs a bot. A code intended for the newsletter can appear on coupon sites and bargain forums shortly after it is sent. From then on, visitors it was not meant for redeem it, and the promotion costs more than planned. Technically this is not an attack, but in monitoring it still looks like one: redemptions of a code jump, new visitors arrive via external referrers, and the share of existing customers among redeemers falls.
The most effective countermeasure is the choice of code type. Shopware distinguishes fixed promotion codes, which are the same for all customers, from individual codes, which can be redeemed only once and are rejected if entered again (Shopware documentation). Fixed codes can be limited via the fields for maximum total uses and uses per customer (Shopware documentation). For individual codes there is one more rule: if the codes of a promotion are subsequently adjusted and regenerated, the previous ones become invalid (Shopware documentation). In normal operation that is a trap, in a case of abuse it is a tool – provided the legitimate recipients receive new codes afterwards.
| Code type | Typical use | Risk in Black Week | Protection |
|---|---|---|---|
| Fixed code | Banner, advert, social channels | Spreads freely, redemptions without an upper limit | Total and per-customer limit, short validity, minimum order value |
| Individual code | Newsletter, loyalty, partners | Enumeration of the series | Enough random positions, rate limit, alert on failed attempts |
| New-customer code | Registration, welcome email | Multiple accounts | Signals beyond the account, review before shipping |
| Channel code | Partners, collaborations, affiliates | Passed on outside the channel | Separate code per channel, analysis by origin |
For detection, it pays to look at where redemptions come from. If redeemed codes from one channel suddenly carry referrers from external sites, or if accounts without a newsletter subscription redeem a newsletter code, the code has leaked. The response depends on the damage: set a total limit, end the code early, or block it for new carts and send the legitimate recipients a replacement.
Repeated redemption and concurrent requests
A code that the promotion says applies once is only single-use if the application checks this in the right place. The OWASP Web Security Testing Guide gives the example of a shop that allows a discount only once per transaction and recommends hard controls: invalidating the code at database level or keeping a counter per user in the back end (OWASP Web Security Testing Guide). A check only in the interface, in the cart or in a cached response is not enough.
The second weakness is concurrency. CWE-362 describes a code sequence that needs temporary, exclusive access to a shared resource while a timing window remains in which a concurrently running sequence can modify it (MITRE CWE). Applied to coupons: two orders with the same single-use code are completed at the same moment, both check “not yet redeemed”, and both receive the discount. How narrow this window is in a specific shop depends on version, extensions and database settings. It can only be verified in testing: on the staging environment with orders submitted in parallel, not in production.
This test belongs in the preparation of every larger promotion and, after every update of the core or promotion extensions, in the regression tests. With a small script it can be repeated as often as needed and shows whether the single-use check holds under load before real customers put it to the test in sale week.
Closing the promotion cleanly
Some promotions end on paper and carry on in the shop. Shopware limits a promotion via the “Valid from” and “Valid until” fields and lets it be deactivated with a switch (Shopware documentation). Both have to match the published end of the promotion, including the time. If the advert says “until Monday, 23:59”, the end of validity has to hit exactly that time in the shop’s time zone. Once the promotion ends, the analysis begins. A short list closes the promotion cleanly:
- End of validity checked: a test cart rejects the code after expiry.
- A new promotion created for any follow-up, instead of reactivating the old one together with its old codes.
- Redemptions analysed per code, account and shipping address, anomalies noted for the next promotion.
- Failed attempts at the coupon field monitored after the end as well: a rise reveals bots still working through old code lists.
- Thresholds and on-call cover reset to normal operation, exceptions from the change freeze documented.
Schedule up to 27 November
Between mid-October and Black Friday there are six calendar weeks. The order of the steps follows a simple logic: only once it is clear which codes exist can the brake be sized, and only once the brake is in place can the alert be calibrated.
Weeks 42 to 43: inventory
Record all planned promotions with code type, channel, validity and expected number of redemptions. Check running long-term promotions and old code series and close them where necessary.
Week 44: generate codes
Create individual codes with enough random positions, give fixed codes total and per-customer limits, and extend new-customer rules with signals beyond the account.
Weeks 45 to 46: brake and alert
Set up the rate limit at the coupon field, record failed attempts as a metric and derive the threshold from previous weeks. Test the single-use check on staging with parallel orders.
Week 47: dry run
Simulate an attack with a few hundred attempts on staging: does the brake kick in, does the alert arrive, can the on-call team see where the attempts come from?
Weeks 48 to 49: sale week
Tighter thresholds, short response paths, daily analysis of redemptions. After Cyber Monday on 30 November, close the promotion according to the list.
Record all planned promotions with code type, channel, validity and expected number of redemptions. Check running long-term promotions and old code series and close them where necessary.
Create individual codes with enough random positions, give fixed codes total and per-customer limits, and extend new-customer rules with signals beyond the account.
Set up the rate limit at the coupon field, record failed attempts as a metric and derive the threshold from previous weeks. Test the single-use check on staging with parallel orders.
Simulate an attack with a few hundred attempts on staging: does the brake kick in, does the alert arrive, can the on-call team see where the attempts come from?
Tighter thresholds, short response paths, daily analysis of redemptions. After Cyber Monday on 30 November, close the promotion according to the list.
Anyone who adds protection only in sale week changes the shop at a time of high revenue and scarce staff. Preparation in October, by contrast, mainly takes planning and testing on the staging environment.
What maintenance takes care of
For shop operators, code abuse is rarely a daily topic. Without a dedicated metric it can easily surface only in the monthly report, when the margin of the promotion is already gone. Under a maintenance contract it is part of ongoing operations: our monitoring analyses web server and application logs for critical and suspicious access patterns, alert thresholds are configured individually for the shop, and for Black Friday and the Christmas season we shorten check intervals, tighten thresholds and extend on-call cover.
Network-level defence is part of security updates: we configure and maintain WAF rules, test updates in advance on staging including the checkout, and add protective measures when a new attack pattern appears. A dedicated brake in the application extends the shop’s functionality and is therefore offered as a separate project; Shopware maintenance then includes it in the tests after every update. Response times and on-call cover during sale week are governed by the SLA maintenance contract.
A metric, not a hunch
Failed attempts at the coupon field per minute and origin, compared with the curve of previous weeks.
Thresholds for sale week
Tighter alert limits and extended on-call cover around Black Friday and Cyber Monday.
Checks after updates
After updates of the core and extensions, the single-use check and the brake belong in the tests on staging.
If an attack still gets through, the procedure from emergency support begins: block the code, tighten the brake, review redemptions and check affected orders before shipping. An overview of all components of our support is on the services page.
Related Articles
WordPress Security 2026: Hardening and Updates
Secure WordPress in 2026: core hardening, plugin security, brute-force protection, Web Application Firewall and security monitoring practical guide.
Stale cache: wrong prices, other people's carts
Page, object and edge caches drift apart: the shop shows one price, the checkout calculates another. How to measure the drift and shut it down.
Disk space monitoring before the shop stops
A full data partition does not cause an outage, it causes stuck orders. Which directories grow, which threshold works and what a dated cleanup cycle covers.