Guide

Hundreds of Fake $1 Donations Overnight: Stopping a Card-Testing Attack on Your Donation Form

You opened the laptop to 400 failed 50-cent donations, an email from Stripe, and a donor list full of names you have never heard of. Your website was not hacked. Here is exactly what happened, what to do in the next hour, and how to make sure it does not happen again.

Short answerHundreds of tiny failed donations means bots are using your form to test stolen card numbers. Your site was not hacked. Pause the form, export the junk rows before deleting anything, then layer defenses: set a minimum donation amount, turn on per-IP rate limiting, add Stripe Radar velocity rules, block bots at your CDN, and use a honeypot. Delete only failed and pending rows, never completed ones.

What is a card-testing attack, and did someone hack my website?

No. Nobody broke into your WordPress site. What happened is that a bot found your public donation form and started using it as a free card-validation tool.

Criminals buy lists of stolen card numbers in bulk. Most of those numbers are dead. Before they can be resold or used for a real purchase, someone has to find out which ones still work. The cheapest way to test a card is to push a tiny charge through a public payment form and watch whether the processor says yes or no. The charge is not the point. The answer is the point.

Donation forms are the preferred target for four reasons, and every one of them is something you built on purpose:

  • Any amount is plausible. A $1 gift to a food bank looks completely normal. A $1 order on an e-commerce store does not.
  • No shipping address, no inventory, no fulfillment. Nothing to slow the bot down and nothing that fails downstream.
  • The form is public and always on. No login, no cart, no account creation.
  • Nonprofits rarely have a fraud team. The bot expects nobody is watching at 3am.

This is an attack on the card networks that is being routed through your form. That distinction matters emotionally and it matters practically, because the fix is not "clean the malware." The fix is to make your form an expensive, slow, unrewarding place to test cards.

What should I do in the first hour?

Work in this order. The most common expensive mistake is deleting evidence or mass-refunding before understanding what you are looking at.

  1. Screenshot and export first. Export your donations to CSV and take screenshots of the Stripe or PayPal dashboard showing the burst. If your processor later asks questions, or if you need to request fee reversals, this record is the whole argument.
  2. Stop the bleeding. Unpublish the donation page, remove the shortcode temporarily, or put the form behind a maintenance notice. Some operators disconnect the gateway keys entirely. That is heavy-handed but it works, and a donation page that is down for two hours costs less than a page that keeps feeding the attack.
  3. Read the actual statuses before you refund anything. The overwhelming majority of card-testing attempts fail. A failed charge has no money to refund. Refunding is only relevant for attempts that actually succeeded, and even then you should refund quickly rather than wait for a dispute, because a dispute typically costs a fee on top of the reversed amount. Stripe's published pricing lists a dispute fee of $15.00 per dispute received (verify current figures at stripe.com/pricing, checked August 2026).
  4. Tell your processor you know. An open ticket that says "we were card-tested, here is what we have already changed" reads very differently than silence.
  5. Do not delete database rows yet. The cleanup section below explains how to do it without wrecking your totals.

Should I set a minimum donation amount to stop card testing?

Yes, and it is the cheapest fix nobody mentions. Card testers use the smallest amount that will authorize, because they are paying the fees on their own stolen cards and volume is the whole business model. Attacks in the 50-cent to $1 range are the norm. Raising your floor to $5 or $10 does not stop a determined attacker, but it removes the economic reason to pick your form over the thousands of others with no floor at all.

In Donor Merchant this lives at Settings > General > Minimum donation. The value is stored as donor_merchant_min_amount and defaults to 1. It is enforced in two places: on the form itself, and again server-side in the REST donate handler, so a bot that posts straight to the endpoint and skips your HTML gets rejected the same way. Client-side-only minimums are decorative against bots.

Practical note: for most small nonprofits, moving the minimum from $1 to $5 costs you almost nothing in real gifts. Check your own history before you decide, and if you genuinely receive $2 gifts, raise the floor temporarily during an active attack and lower it later.

Is there a WordPress donation plugin with built-in rate limiting on the donation endpoint?

Yes. Donor Merchant enforces a per-IP hourly cap on the public donate endpoint in core, with no add-on and no configuration required. By comparison, a third-party analysis of GiveWP states plainly that GiveWP does not include built-in per-IP or per-email rate limits, which is why so many of those threads end with "install a separate anti-spam plugin."

Here is the actual behavior, verified in the source:

EndpointDefault capHow to change it
Public donate endpoint15 attempts per IP per hourdonor_merchant_rate_limit filter
Donor portal magic-link request8 per IP per hourSeparate bucket, fixed
Contact form5 per IP per hourSeparate bucket, fixed

A developer can tighten the donate limit to, say, 5 per hour with a one-line filter in a site-specific plugin. Separate buckets matter: a flood against the donate endpoint should never lock a real donor out of their receipt.

Be honest about what rate limiting does and does not stop. It reliably defeats a single-source flood, which is the most common shape of these attacks. It does not stop a distributed botnet spreading 20,000 attempts across thousands of residential IPs, where each individual address stays under your cap. That is why this is layer two of five and not the whole answer.

Cloudflare users, read thisIf your site sits behind Cloudflare or any reverse proxy and you have not restored the real visitor IP, your rate limiter is keying on the proxy address. Every visitor looks like the same person, which either blocks everyone or, more commonly, is configured loosely enough that it blocks nobody. Enable Cloudflare's real-IP restoration at the server (mod_remoteip or the equivalent for your stack) and confirm it before trusting any per-IP rule.

Do I have to pay Stripe Radar fees for attempts that were blocked?

This is the part that makes people furious, and the answer is that it depends on your Radar plan. Stripe's built-in fraud tooling is included at no extra cost on standard pricing, but the advanced tier (Radar for Fraud Teams), which is where custom velocity rules live, is priced per screened transaction. Screened includes attempts that were blocked.

One nonprofit described exactly this outcome in a public WordPress.org thread: after noticing "hundreds of fraud donations in amounts from 50 cents to $10," they were pushed onto Radar for Teams and then paid "$55 in Radar processing fees for the hundreds of blocked attempts."

The lesson is not to avoid Radar. Radar rules are genuinely the strongest gateway-side layer you have, and the rules worth writing are velocity rules: block when more than N attempts come from the same IP in an hour, the same email in a day, or the same card fingerprint across multiple attempts. The lesson is that gateway-side blocking has a per-attempt cost, so the layers above it (minimum amount, rate limiting, edge blocking) are the ones that save you money, because traffic they stop never reaches Stripe to be screened at all. Confirm current Radar pricing on Stripe's pricing page before enabling anything.

How do I block bots before they ever reach WordPress?

Every attempt that reaches PHP costs you a database write, a gateway API call, and possibly a screening fee. Blocking at the CDN edge costs you nothing.

If you use Cloudflare, three things are worth turning on today:

  • Bot Fight Mode (available on the free plan) challenges obvious automated traffic before it hits your origin.
  • A rate-limiting rule scoped to your donation endpoint path, for example a cap of a handful of POST requests per IP per minute to /wp-json/donor-merchant/v1/donate.
  • A WAF rule blocking traffic from countries or ASNs you have never received a real gift from. Use this carefully and revisit it, since it will block real people too.

Other CDNs and managed WordPress hosts offer equivalents. If you are on shared hosting with no edge layer, ask your host what request-level rate limiting they can apply to a specific URL. Many can.

What about honeypots and CAPTCHA?

A honeypot is a hidden form field that humans never see and never fill in. Bots that parse and complete every input give themselves away instantly. It costs nothing, it is invisible to donors, and it catches a meaningful share of low-effort automation. Donor Merchant uses this pattern on its contact form.

CAPTCHA is a last resort, and you should reach for it only during an active attack. A donation form is the single worst place on your website to add friction. Every extra step between "I want to give" and "given" loses gifts, and the losses fall hardest on older donors, screen reader users, people on slow rural connections, and anyone on a phone. If you have ever watched an 80-year-old major donor try to identify traffic lights in a grid of blurry squares, you know the cost is real. If you must use one, prefer an invisible or score-based challenge over an interactive puzzle, and take it back off once the attack subsides.

How do I clean up hundreds of spam donations without breaking my donor totals or year-end statements?

This is the part nobody writes about, and it is where the real damage happens. Do not blind-delete, and do not "select all, delete."

The concern is that junk rows attached to fabricated email addresses create fabricated donor records. Those donors then show up in your CSV export, in your mailing list sync, and in your annual statement run. A statement mailed to a fake address is harmless. A donor count reported to your board that is inflated by 400 phantom people is not, and a year-end statement that credits a real person with donations they never made is a genuine problem.

Here is the safe procedure in Donor Merchant. The same logic applies to any plugin that stores a status per donation:

  1. Export everything to CSV first. Donor Merchant's export also neutralizes spreadsheet formula injection in exported cells, so a malicious value in a name field cannot execute when you open the file in Excel.
  2. Filter by status. The Donations screen has status views for All, Completed, Pending, Failed, and Refunded. Card-testing junk lives almost entirely in Failed and Pending.
  3. Sort by date and amount and actually look. Scan for anything that could be a real donor whose card was declined for an innocent reason. Those people deserve a follow-up email, not deletion.
  4. Bulk delete the verified junk from the Failed and Pending views only. Never bulk-delete from the All view.
  5. Leave completed donations alone. If a fraudulent charge actually succeeded, refund it through Stripe or PayPal so the status becomes Refunded. Do not delete the row. Deleting a real financial transaction from your records is the one action here you cannot undo.
Why your totals are probably already fineIn Donor Merchant, every money figure is computed from completed donations only. Donor lifetime totals, donation counts, campaign thermometer progress, the reports dashboard, the monthly summary email, and the annual tax statement all filter on status = completed. A pile of failed rows makes your admin list ugly. It does not inflate what your board sees or what a donor receives in January.

What failed rows can do is create empty donor records if the bot supplied an email address. After cleanup, sort your donor list by lifetime total, look at the zero-total records created during the attack window, and remove the ones that are clearly fabricated. Then re-run your year-end statement preview before mailing season and confirm the count matches your expectation.

None of this is tax advice. If a fraudulent transaction has already been receipted or reported, talk to your accountant about the correct way to reverse it in your books.

Will Stripe close my account because of card-testing donations?

A warning email is not a closure notice, and a high decline rate caused by an attack is a situation Stripe's risk team sees constantly. What causes real trouble is ignoring it. Card networks impose penalties on processors for excessive authorization attempts and excessive fraud rates, which is why Stripe escalates quickly.

The path back to good standing is straightforward: respond to the notice, describe the mitigations you have put in place (minimum amount, rate limiting, Radar velocity rules, edge protection), and show that the attempt volume dropped afterwards. Processors are looking for evidence that the hole is closed. Give them that evidence in writing.

The money angle: your platform is charging a percentage on fraud too

State this once, plainly. If your donation platform takes a percentage of every transaction, it takes that percentage on fraudulent transactions as well. A nonprofit that has just been attacked then has to open a support ticket and ask for those platform fees back.

That is not hypothetical. One operator wrote on WordPress.org that they had "nearly 20.000 non legit tries to make a donation, possible to check stolen creditcards," and described 17,000 pending donations, both gateways disconnected, manual refunds in Stripe, and a request to recover the platform application fees charged on the fraudulent transactions. Another reviewer put it more bluntly: "Took more time and money to fix than what we raised."

Donor Merchant takes no platform fee on any transaction, ever. During a fraud wave that means your exposure is whatever Stripe or PayPal charges and nothing more. There is no percentage to claw back, because there was never a percentage. That is the same reason the plugin is free with no platform fee on your good days too.

Your five-layer defense checklist

LayerWhat it stopsCostWhere to set it
Minimum donation amountRemoves the economic incentive to pick your formFreeSettings > General > Minimum donation
Per-IP rate limitingSingle-source floods, not distributed onesFree, built inOn by default, 15/hour on the donate endpoint
Stripe Radar velocity rulesRepeat IP, email and card fingerprintPer screened transaction on the advanced tierStripe Dashboard > Radar > Rules
Edge / CDN protectionTraffic before it reaches PHP or StripeFree tier available on CloudflareBot Fight Mode plus a URL rate-limit rule
Honeypot fieldLow-effort automationFree, invisible to donorsBuilt in; CAPTCHA only as a temporary last resort

Two more items belong on the printed version taped to your monitor. Alerting: turn on admin email notifications so you find out from a notification and not from your bank. Monthly check: once a month, open the Failed view on your donations list and glance at the volume. A slow climb is an early warning that somebody is probing.

Next stepIf you are mid-attack right now: unpublish the form, export to CSV, raise your minimum donation amount, and confirm your real visitor IP is being restored behind your CDN. That takes about fifteen minutes and stops most of it. When things are calm, read the Stripe setup guide to add Radar velocity rules, or see getting started if you are evaluating a move to a plugin where per-IP rate limiting and a server-enforced minimum amount ship in core. Download Donor Merchant free.

Your next monthly donor is on your website right now

Give them a form worth filling out. Install Donor Merchant free and take your first recurring gift today.

Download Donor Merchant free No signup. No platform fees. Or try the live demo first.