2
2 Comments

I run a free URL shortener on the side. Someone did 90k scam clicks in 48 hours.

I already have my main products (KeepTheScore etc.). On the side I run zip1.io — a free URL shortener with no signup. It costs me about $27/month and makes $0. I forked an open-source project, put a domain on it, and mostly leave it alone except for weekly abuse sweeps.

Last week’s sweep found a destination that had taken about 90,000 clicks in 48 hours. More than everything else on the site combined. I deleted the links. Two minutes later they were back.

What followed was three rounds of delete-and-recreate. Because zip1 lets people pick custom slugs, the operators kept reclaiming the same short URLs that were already circulating on Facebook. Deleting wasn’t enough until I started reserving burned slugs so the old posts just 404.

The technical bit that surprised me: the same destination returned three different pages depending on how I fetched it — desktop browser, datacenter server, or Android inside the Facebook app. One of them fingerprinted the device and, if you looked like a bot, played a success animation and then did nothing. Quietly. No error.

I wrote the full post-mortem here (cloaking, who was clicking vs who was creating the links, how reserved slugs fixed the loop):
https://casparwre.de/blog/anatomy-of-a-scam-campaign/

If you run anything with public create / no auth / custom IDs, I’d be curious how you handle the third round of “they’re back.”

on September 18, 2026
  1. 1

    The burned-slug fix is clever because the stable asset wasn’t the destination domain, it was the URL already distributed across Facebook.

    I’d probably add a temporary circuit breaker based on traffic shape: a new anonymous link jumping from zero to thousands of Facebook in-app clicks, or several slugs converging on one redirect chain, gets quarantined for review. The destination will keep changing, but the campaign’s creation and traffic patterns may be harder to replace.

    Do you preserve those relationships after deletion? A campaign graph of slugs, redirect hops, creator IPs and referrers might stop each recreation from looking like a completely new incident.

    1. 1

      Thanks — yes, the Facebook-baked slug was the part that kept surviving every purge.

      On the circuit breaker: create-time already catches a lot (blocklist + Safe Browsing), and the weekly scan scores volume / nested hops / cheap domains after the fact. A live “sudden Facebook in-app spike → quarantine” rule is on my mind; I haven’t shipped it yet because false positives on legit campaigns would hurt a no-signup shortener.

      Relationships after deletion: I reserve the burned slug and expand the destination blocklist, but I don’t yet keep a full campaign graph (slugs ↔ hops ↔ creator IPs ↔ referrers) as a first-class object. That’s a good nudge — the recreate loop would be easier to spot if each “new” incident still linked back to the last one.