Use Case — QR Code Security

Phishing detection for QR codes and quishing

A printed square is a link nobody can read. By the time the phone has decoded it, followed two redirects and painted a login form, the user has already committed to trusting it. Screening the resolved host against a database of DNS-verified, currently-live phishing domains puts a decision point back where the human lost one.

200,000+ DNS-verified phishing domains
04:30 UTC When the daily build lands
100 Domains per batch request
0.98 Confidence on a confirmed hit
Camera & scanner apps
Resolved-host lookup
Pre-press batch screening
MDM & mobile browsers
Guest Wi-Fi blocklists
Estate code audits
Home / Use Cases / QR Code Security (Quishing)
The missing preview

A QR code removes every signal a user normally relies on

Quishing is not a new kind of phishing page. It is the same page delivered through a channel that strips out the inspection step, the gateway and, very often, the managed device as well.

What the scan actually does

Decoding is instant and silent. The camera turns a pattern of black modules into a string, the operating system offers a tap target, and a browser opens. Most people never see the string at all, and when a preview is shown it is truncated to fit a notification banner — usually to the point where the interesting part of the hostname has been cut off.

Nothing you own sits in that path

None of the defences an organisation has spent a decade building are in that path. No secure email gateway rewrote the link. No proxy inspected the request. No desktop hover-preview gave the user half a second to notice that the domain was wrong. Frequently the device is a personal phone that the organisation has never enrolled and cannot see.

Where a check can still be inserted

There is a gap of a few hundred milliseconds between decoding the payload and rendering the destination. That gap belongs to whoever wrote the scanner. It is the last place where software still holds the URL as data rather than as a page that is already loading, and it is the natural place to ask a simple question about the host.

The one question

Is this hostname a currently-live, DNS-verified phishing domain?

The question is deliberately narrow. A single GET /api/v1/check answers it against a database of more than 200,000 active domains, returning is_phishing, a category of phishing/malware, a dns_status of resolves and a confidence of 0.98 on a confirmed hit.

A printed code inherits physical trust

The reason quishing has been so productive for attackers is not technical sophistication. It is that a printed code is trusted the way a physical object is trusted. People have been taught, correctly, to be suspicious of unexpected email attachments and of links that arrive from strangers. Nobody has been taught to be suspicious of a sticker on a parking meter, because for most of the last decade the sticker on the parking meter was genuinely put there by the city.

Two judgements collapse into one act

That trust transfers cleanly to the digital destination. When a user scans a code on a table tent in a restaurant, the mental model is "I am looking at the menu", not "I am about to type my card details into an arbitrary web server chosen by whoever had physical access to this table in the last six weeks". The user's judgement about the code and their judgement about the page have been collapsed into one act, and only one of the two was informed.

It also matters that the destination arrives with no return address. An email carries a sender, headers, an authentication result and a path through infrastructure you control. A QR code carries a string. If it turns out to be malicious, there is nothing to trace back except the physical object, which is anonymous by construction and which the attacker peeled off and threw away a week ago. This is why so many quishing investigations end without an origin: the delivery mechanism does not log.

Why quishing cases go cold

Identical payload, radically different defensive depth

Compare this with the desktop web, where the same underlying credential-harvesting kit is often blocked by three independent layers before it renders. On the phone, after a scan, there is usually nothing. That asymmetry is why an organisation that has already invested in email gateway screening and DNS-level filtering can still be losing credentials through a channel it has never instrumented.

Where the codes live

Six places a malicious code reaches a phone

The delivery surfaces are wildly different in look and feel, but every one of them ends in the same event: a hostname resolved by a phone with no gateway in front of it.

Sticker-over-sticker on street furniture

Parking meters, EV charge points and bike-share docks all carry a payment code, and all of them sit unattended in public. Printing a vinyl sticker at the same dimensions and pressing it over the original takes seconds and leaves no trace beyond an adhesive edge nobody inspects.

Restaurant table tents and menus

Hospitality normalised scanning a code to see a menu, then to pay. A table tent is small, replaceable and handled by hundreds of strangers a week. The resulting page can ask for a card number without looking remotely out of place, because that is what the real one does too.

Codes inside PDF and image attachments

Link rewriting operates on hyperlinks. A code rendered as pixels inside an attached PDF, PNG or embedded signature image contains no hyperlink to rewrite, so it travels through untouched and is read by a second device the mail system cannot see.

Conference badges and event signage

Attendees expect to scan at events: to join the Wi-Fi, download the agenda, exchange contact details or claim a session recording. Signage is temporary, produced in a hurry by a third party, and installed by people who cannot verify what any of it points at.

Printed invoices and utility bills

"Scan to pay" on paper correspondence is now routine for utilities, councils and healthcare billing. A convincing counterfeit bill posted to a household needs no infrastructure at all beyond a domain and a payment form, and it arrives with the authority of physical mail.

MFA-enrolment lures

Genuine authenticator enrolment uses a QR code, which has trained users to scan one during a security workflow. An email claiming that multi-factor authentication must be re-registered, with a code attached as an image, borrows that muscle memory precisely when the user is most compliant.

The attacker chooses the placement, not the delivery

What unites these is that the attacker chooses the placement, not the payload delivery. In email phishing the adversary has to get past your filters. In quishing they have to get past a maintenance worker who is not looking, or a print vendor who does not check, or a conference organiser with four hours until doors open. The economics are attractive because the physical placement cost is near zero and the reach is whoever walks past.

Email phishing versus quishing
DimensionEmail phishingQuishing
What must be defeatedGateway filtering, URL rewriting, attachment detonationA maintenance worker, a print vendor or an event organiser
Cost per extra targetCheap, but throttled by sending reputationEffectively zero — one sticker reaches everyone who walks past
Device that opens itUsually managed, usually loggedUsually a personal phone with no enrolment
Forensic trailHeaders, authentication results, transport logsAn anonymous printed object and nothing else
Durable indicatorSender, subject, hash, destination hostThe resolved destination host

The mail system sees an image; the phone sees a URL

The attachment case deserves special attention because it is where quishing intersects most awkwardly with existing controls. A security team can reasonably say that their mail path inspects links, detonates attachments and rewrites URLs. All of that remains true and none of it helps, because the malicious instruction is an image of a link, and the click happens on a device that is not part of the conversation. Nothing sees both halves.

Highest-impact variant

The MFA re-enrolment lure

The MFA-enrolment variant is the most damaging in practice because of what it collects. A user who believes they are re-enrolling an authenticator will not only supply a password, they will actively co-operate with a prompt for a one-time code, and they will do so quickly because security workflows feel urgent. That is a full session takeover delivered by a user who thinks they are doing exactly the right thing, which is why awareness training alone cannot carry this problem.

Every surface terminates at a hostname

None of this changes the shape of the technical defence. Whatever the surface, whatever the pretext, the attack terminates at a hostname. If that hostname is already known and currently resolving as phishing infrastructure, the scanner has everything it needs to refuse before the page is fetched.

Operational reality

You cannot patch a sticker

Every other phishing control benefits from software's great advantage: you can change it everywhere at once. A printed code is immune to that, and planning around the difference changes what "response" means.

Mean time to remediate is measured in van journeys

Consider what happens when a malicious code is discovered on a payment terminal in one of two hundred sites. In a software incident you would push a rule and be done. Here, the artefact is a physical object bonded to a physical asset in a location that may be unstaffed, and remediation means dispatching a person with a scraper. Until that person arrives, the code keeps working, and everyone who scans it in the interval is exposed.

You rarely know how many copies exist

The attacker who covered one meter almost certainly covered a dozen, distributed across a district according to footfall rather than according to your asset register. The first report tells you the campaign exists; it tells you nothing about its extent. This is the inverse of the usual situation, where you can query for the indicator and get a complete list in seconds.

Only the destination can be fixed everywhere at once

The practical consequence is that physical remediation should never be your only control, because it cannot be fast and cannot be exhaustive. What can be both is the destination. Peeling the sticker removes one instance; getting the destination host recognised as phishing infrastructure neutralises every instance simultaneously, including the ones you have not found, for every user whose scanner consults a threat source. The physical clean-up still has to happen, but it stops being the thing standing between your customers and a credential form.

Why a DNS-verified rebuild keeps the list proportionate

The daily rebuild that lands at 04:30 UTC only includes domains with an active A record, so a domain that has been abandoned drops back out and stops consuming attention. For an estate operator, that means the list you push to venue Wi-Fi filtering or to your inspection tooling stays proportionate to what is actually live, rather than accumulating a decade of dead indicators. The same discipline that makes the feed useful for DNS filtering makes it useful here.

Because the physical artefact is anonymous, the destination host is frequently the only durable evidence you will ever have. Photograph the code in place, record the decoded payload, record the full redirect chain and the final host, and keep the API response including its last_checked date. That package is what lets you demonstrate later that a specific site was compromised on a specific day, which matters for insurers, regulators and any eventual incident response narrative.

Second-order point — evidence handling
Plan for partial knowledge. Assume every discovered code is one of several. Treat the first find as a trigger for a route-wide inspection sweep, not as a closed ticket.
Separate the two clocks. Physical removal runs on a maintenance schedule you do not control. Destination screening runs on a schedule you do. Do not let the slow clock set your exposure window.
Capture before you scrape. The decoded payload and final resolved host are the only artefacts that survive removal. Record them before the sticker goes in the bin.
Redirect chains

Unroll the shortener before you check anything

The single most common integration mistake is checking the domain that was encoded in the code. Almost none of the interesting ones encode the destination directly.

Scan decodes payload
Shortener 301 / 302
Final resolved host
API verdict

Why the encoded host is almost never the destination

A QR code has a payload size limit, and every extra character makes the module grid denser and harder to scan from a distance or in bad light. That practical pressure pushed the whole ecosystem towards shorteners years ago, long before anyone was thinking about abuse. The result is that the host encoded in the image is usually a link-shortening or campaign-tracking service, and checking it tells you only that the shortener exists.

Step 1 — unroll

Follow the redirects yourself

Issue the request with redirects disabled, read the Location header, and follow it yourself with a hard cap — five hops is generous, and anything longer is itself a signal worth surfacing. Extract the host from the final URL, normalise it to lowercase, strip any port, and pass that to /api/v1/check. Do not send the full URL and hope: the endpoint will strip http://, https:// and path segments for you, but it cannot un-shorten on your behalf, because that requires a network request from your side.

Step 2 — check

Submit every hop, not just the terminal host

A chain that passes through a domain already flagged as phishing infrastructure is suspicious even if the terminal host is unknown, because attackers reuse redirector infrastructure far more readily than they reuse landing pages. With a batch of up to 100 domains per request at one lookup each, submitting an entire chain costs the same as three or four individual lookups and gives you a far more complete picture.

Step 3 — cache

Cache the payload-to-host mapping

A shortened link that resolves to the same host for every user is a natural candidate for a short-lived local cache keyed on the shortened URL. This is where the arithmetic gets friendly: a venue whose printed campaign generates tens of thousands of scans a day is resolving a handful of distinct destinations, so the number of distinct hosts you actually need to check is orders of magnitude smaller than the number of scans.

Two failure cases that need explicit handling

First, cloaking: some destinations serve a redirect to a benign page when the requesting client looks like a scanner or a datacentre IP, and the real payload only to a mobile user agent. If your unroller always presents as a server, you will systematically see the safe branch. Second, timing: a chain that hangs is a denial-of-service against your own scan path. Set an aggressive timeout, fail towards showing the user a warning rather than silently opening, and never let the check block rendering indefinitely.

Cap the hops. Five redirects is a working limit. Treat an over-long chain as a signal in its own right and surface it to the user rather than silently following it.
Check every hop, not just the last. One batch request covers up to 100 domains at one lookup each, so a full chain costs almost nothing and catches reused redirector infrastructure.
Integration sequence

Putting the check between the decode and the "Open" button

The order of these steps matters more than any individual one. Each stage is cheap on its own; together they turn an unmediated scan into a decision with evidence behind it.

1

Decode, then pause

Hold the decoded string as data. Do not hand it to the system URL handler yet. This is the whole opportunity: once the OS opens it, the request has left your process and the destination is being fetched by a browser you no longer control.

2

Resolve the chain server-side

Send the payload to your own backend, which follows redirects with a hop cap and a short timeout. Keeping this on the server matters twice over: it stops the phone making requests to hostile infrastructure, and it keeps your API key off the device, which the API documentation is explicit about.

3

Check every host in the chain

Batch the hosts you collected into a single POST /api/v1/batch call. The response gives you per-domain results plus checked, phishing_found and credits_used, which is exactly what you want to log alongside the scan event.

4

Render the verdict, not the page

On a hit, replace the "Open" affordance with an interstitial that names the host and says plainly why it is blocked. On a miss, show the resolved final host in full before the tap target, because the point of the whole exercise is to give back the preview the format removed.

5

Feed the outcome back

Log the payload, the chain, the final host and the verdict. Aggregate by location if your product knows where the scan happened. A cluster of identical malicious destinations scanned within a few streets of each other is a physical campaign, and that is actionable in a way a single event never is.

# 1. unroll, capped at 5 hops, then take the final host
FINAL=$(curl -s -o /dev/null -w '%{url_effective}' -L --max-redirs 5 \
 --max-time 4 "https://short.example/x7Kq2")
HOST=$(printf '%s' "$FINAL" | awk -F/ '{print $3}')

# 2. ask the one question that matters
curl -s "https://phishingdetectionapi.com/api/v1/check?domain=$HOST&apikey=YOUR_KEY"

# {"domain":"..","is_phishing":true,"category":"phishing/malware",
# "dns_status":"resolves","confidence":0.98,"last_checked":"2026-07-27"}
Before it goes to press

Screening campaigns, estates and guest networks

Scanner-side checking protects the person holding the phone. These three workflows protect everyone else — the people who print the codes, the people who own the walls they hang on, and the network they connect to.

1. Screen the print run

Start with the print run, because it is the cheapest possible intervention. A marketing team producing a campaign has a finite list of destination hosts, usually fewer than a dozen and often exactly one. Screening them before the artwork goes to press costs a handful of credits and eliminates the scenario where a brand ships fifty thousand pieces of print pointing at a domain that a partner agency registered, let lapse, and which somebody else has since picked up. The check takes seconds; the reprint takes a quarter.

2. Reuse the list as an allowlist

If your organisation knows the exact set of hosts its own codes are supposed to reach, a scanner or an MDM policy can treat anything outside that set as worth a second look. This is the strongest possible signal in a venue context: a stadium that publishes four campaign domains has no legitimate reason for a code on its own concourse to resolve anywhere else. The API answers "is this known-bad"; the allowlist answers "is this ours"; the interesting cases are the ones that are neither.

3. Audit the estate on foot

Estate audits close the loop on the physical side. Give inspection staff a scanner app that logs the decoded payload and final host of every code they walk past, then reconcile that log against the allowlist on a schedule. Fuel forecourts, transit stations, car parks and hospital concourses all already run routine physical inspections; adding a scan takes seconds per asset and turns an invisible problem into a report. For a large estate, batching the collected hosts 100 at a time keeps the whole audit within a trivial credit budget.

For guest Wi-Fi, take the feed rather than the API

The daily threat feed ships the full CSV with domain,category,dns_status columns plus a changelog of what was added and removed, which is exactly the shape a DNS sinkhole wants. Load it into Pi-hole, BIND RPZ or Unbound once a day after the 04:30 UTC build and every device on the guest network inherits the protection, including the ones that scanned a code before you ever heard of it. The feed subscription is a flat $499 per month, so the marginal cost of covering an extra site is zero.

$59 Starter — 10,000 credits

The plan arithmetic, concretely

Since the alternative to the feed is per-lookup billing, it is worth being concrete. Pre-press screening and estate audits are naturally small: a few thousand lookups a year, which the Growth package at 10,000 credits covers comfortably, with lookups reset monthly. Scanner-side checking is the workload that scales with users, and that is where local caching plus the feed matter — matching against a locally loaded list costs nothing per scan, and you spend API credits only on hosts the local copy has never seen. The pricing page lists every package above that.

Honest limits

What a known-bad lookup does and does not tell you

This service answers one question precisely. Designing around its actual boundaries produces a better system than pretending it answers a broader one.

Read the verdict precisely

A hit is a strong statement; a miss is a weak one

The database contains domains that have been confirmed as phishing infrastructure and verified by DNS as currently resolving. A hit is therefore a strong, specific statement: this host is live phishing infrastructure, categorised phishing/malware, with a confidence of 0.98. A miss is a much weaker statement. It means the host is not in the database today. It does not mean the host is safe, and no responsible integration should render it as a green tick.

Physical campaigns often run on brand-new domains

That distinction bites hardest in exactly the scenario quishing creates. Physical campaigns often run on domains registered days before the stickers go up, precisely because the operator expects the domain to be burned quickly. A brand-new domain that nobody has reported yet will not be in any known-bad list, including this one. This is not a gap peculiar to this product; it is the structural limitation of reputation lookups as a class, and any vendor claiming otherwise is describing a different kind of system.

So pair the lookup with signals you can compute yourself. Domain age, whether the registrable domain matches your own allowlist, whether the chain crossed more hops than expected, whether the final host differs from the host encoded in the code, whether the page is being requested from a location where you have no campaign running. None of these are verdicts on their own, and all of them are cheap. The API supplies the one input you cannot produce yourself — a continuously maintained view of what is confirmed bad right now — and your own context supplies the rest.

Designing around the boundary

Scope in the other direction: per domain, not per page

A compromised path on an otherwise legitimate host will not be flagged. There is no screenshotting, no brand-logo matching and no machine-learning judgement about an unknown page. What there is, is a database whose freshness is enforced: entries that stop resolving fall out at the next rebuild, which is why the active count sits above 200,000 while the all-time total is far larger. That churn is the feature. It is also why teams building threat intelligence pipelines tend to treat this as a high-precision enrichment source rather than a sole arbiter.

A hit is decisive. Confidence 0.98, category phishing/malware, DNS status resolves. Block it and say why.
A miss is not clearance. Show the resolved host, keep your own signals in play, and never render "not found" as "verified safe".
Questions we get

Building QR screening in practice

The questions below come up in nearly every conversation about instrumenting a scan path.

Should I check the domain encoded in the code, or the one it ends up at?

Both, but the final resolved host is the one that decides. Encoded hosts are overwhelmingly shorteners and campaign trackers, so checking only that tells you almost nothing about the destination. Follow the chain server-side with a hop cap, collect every host you pass through, and submit them together in one batch call. Checking the intermediate hops is worth the extra credits because attackers reuse redirector infrastructure much more readily than they rebuild landing pages, so a flagged hop is often your earliest warning even when the terminal host is still unknown.

Practical note

Log both hosts against the scan event even when the verdict is clean. A divergence between the encoded host and the final host is not suspicious on its own — that is what a shortener does — but a sudden change in where a long-running printed campaign resolves to is one of the earliest signals that a code has been overlaid or a campaign domain has changed hands.

Can I call the API directly from the mobile app?

No. The API key is the username chosen at registration and it must never appear in client-side code, because anything shipped to a device can be extracted from it. Route the check through your own backend, which also gives you the place to unroll redirects, cache repeated destinations, enforce timeouts and apply your own allowlist. The rate limit is 10 requests per second per key, which is a further reason to funnel through a server you control rather than letting thousands of handsets each hold a key.

What happens to scan latency?

The dominant cost is unrolling the redirect chain, not the lookup, because following hops means talking to infrastructure you do not control. Cap the hops, set a short timeout, and cache the payload-to-host mapping: a printed campaign generates enormous scan volume against a tiny number of distinct destinations, so most scans should be answered from cache. If the chain times out, fail towards an interstitial that tells the user the destination could not be verified rather than silently opening it.

How do I cover a whole venue rather than a single app?

Take the daily feed and load it into the resolvers behind your guest Wi-Fi. The CSV carries domain,category,dns_status and ships with a changelog of additions and removals, which fits Pi-hole, BIND RPZ, Unbound and commercial DNS appliances directly. Sync after the 04:30 UTC build. This covers every device on the network regardless of which scanner app it uses, which is the only realistic way to reach guests who will never install anything of yours.

Where the feed stops

Resolver-level blocking only reaches devices actually using your DNS, so a phone on mobile data walks straight past it. Treat the feed as the floor for a venue and the scanner-side or MDM check as the layer that follows the user off the network.

The domain came back clean. Is the code safe?

It means the host is not currently in a database of confirmed, actively resolving phishing domains. That is genuinely useful and it is not the same as safe. Domains registered specifically for a physical campaign may be too new to have been reported anywhere. Present a clean result as "not known to be malicious", show the user the full resolved hostname, and keep your own signals — domain age, allowlist match, unexpected chain length, geography — in the decision.

We print codes ourselves. What should we screen?

Every destination host in the run, before artwork approval, including any shortener or tracking domain the chain passes through. It is a handful of credits against the cost of a reprint. Then publish that list internally as your allowlist so scanners, MDM policies and inspection staff have something to reconcile against.

What it costs to start

A pre-press workflow is one of the smallest possible workloads: a dozen hosts per campaign, a few hundred lookups a year. The Growth package at 10,000 credits, billed monthly, covers a print operation and its estate audits together with room to spare.

Does this replace user education about scanning?

No, and the two work on different failure modes. Education helps with pretexts — why an unexpected MFA re-enrolment email deserves suspicion. It cannot help with a hostname the user was never shown, which is the specific thing the format takes away. Restore the preview, block the confirmed-bad destinations automatically, and let training work on the judgement calls that remain. Teams already running awareness programmes usually find the scan telemetry gives them far better material than generic examples.

Put a verdict between the scan and the tap

Pick a monthly plan and wire a single lookup into the moment after decoding. Screen a print run, audit an estate, or sync the daily feed into the resolvers behind your guest network — all of it against a database of DNS-verified phishing domains rebuilt every 24 hours.