One QR code that opens the app, the store, or the web.
A code on a box, a badge, or a shelf talker is scanned by people who have your app and people who have never heard of it. The same printed square has to work for both, and still tell you which placement earned the install.
A QR code just encodes a URL, so making one open your app is a deep linking problem rather than a QR problem. Encode a Ferry link and the same printed code opens the app for people who have it, sends everyone else to the right app store, and shows a web page on desktop. The scan is recorded either way, and when a scanner installs the app, Ferry matches that install back to the exact code they scanned.
Print once. Decide the destination later.
Physical placements have a property no digital channel has: you cannot change them after they ship. That makes the indirection a Ferry link gives you worth more here than anywhere else.
- Encode a link, not a scheme
A custom scheme such as acme:// does nothing for a visitor without the app, and many camera apps refuse to open one at all. Encode the https Ferry link. It is a single URL that has an answer for every device that scans it.
- Print once, repoint later
The destination lives in the link payload, not in the printed image. You can repoint a code already on shelves to a new screen, a new promo, or a new landing page without reprinting anything.
- One link per placement
Create a separate link per print run, table tent, or booth with campaign source, medium and name set. Scans on the box and scans on the poster then arrive as separate rows instead of one number nobody can act on.
- The install still carries context
For physical placements, a scan that leads to a store visit is the normal case, not the edge case. Ferry keeps the payload through that install and delivers it on first launch, so a code printed next to one product can open that product.
- Desktop and older devices still land somewhere
Some scans come from a laptop webcam or a device that cannot install your app. The same URL falls back to the project web destination there rather than a store page that cannot help.
# Create one link per placement, then encode its URL in the QR code.
curl -sS -X POST "https://api.ferrylink.io/v1/links" \
-H "Authorization: Bearer $FERRY_SK" \
-H "Content-Type: application/json" \
-d '{
"projectId": "PROJECT_ID",
"domainId": "DOMAIN_ID",
"payload": { "data": { "screen": "product", "product_id": "sku_123" } },
"campaign": { "source": "packaging", "medium": "qr", "name": "spring_box_v2" }
}'
# The response carries the slug. Encode https://acme.feryl.io/<slug> in the code.
# In the app, the same Ferry.onLink callback receives that payload, whether the
# scanner already had the app or installed it because of the scan. Ferry does not generate the image. Use any QR encoder on the returned URL. The sk_ secret key is server-side only and never belongs in an app, a bundled config, or the print pipeline.
Print is unforgiving.
A broken link in an email is a bad afternoon. A broken link on 40,000 boxes is a quarter. These are the mistakes worth catching before the print run.
| What goes wrong | What to do about it |
|---|---|
| The code encodes a custom URL scheme. | Camera apps and third-party scanners handle acme:// inconsistently, and it does nothing at all for a visitor without the app. Encode the https link so every scanner has something valid to open. |
| A URL shortener sits in front of the Ferry link. | Every extra hop is another chance for a scanner or in-app browser to break the chain, and iOS decides whether to open your app based on the URL the scan actually targeted. Encode the Ferry URL directly. |
| One code for the entire campaign. | You lose the ability to tell the box from the poster from the event badge. Create a link per placement with its own campaign fields, and compare them in the dashboard. |
| The code is sized for the wrong scan distance. | Module size drives readable range. A code that scans on a phone at desk distance often fails on a shelf two metres away. Test at the real distance and lighting before committing to the run. |
| The venue has no usable connectivity. | Resolving a link needs the network. In a basement conference hall or a store with poor signal, the visitor gets a browser error instead of your app. Choose placements where connectivity is reliable, or expect the drop. |
| Scans are reported as installs. | A scan is a link open. Ferry counts the install separately, and only when a first launch actually matches back to that open. The ratio between the two is the number worth reporting. |
Questions developers ask first.
How do I make a QR code that opens my app? +
Encode an https deep link rather than a custom scheme. A universal link or App Link opens the installed app straight from the camera, and everyone else follows the same URL to the right app store or a web page.
What happens when someone scans and does not have the app? +
The link routes them to the App Store or Play, and Ferry keeps the payload. When they install and open the app, Ferry matches that first launch back to the scan and delivers the payload with isDeferred set to true, so a code printed on one product can still open that product.
Can I change where a printed QR code goes? +
Yes. The printed code holds a Ferry URL, and the destination lives in the link rather than in the image. Change the payload or destination in the dashboard and every future scan follows the new one, with no reprint.
Do I need a separate QR code product? +
No. Any encoder can turn the URL into an image. The hard part of a QR to app journey is the routing and the install gap, and both of those belong to the link, not to the code.
Can I attribute revenue to a physical placement? +
Yes, when the app reports purchases through the SDK. Ferry ties the install and the events that follow back to the link that was scanned, so a shelf talker or a box can be compared in revenue rather than in scan counts.
Do scans by scanners and previews count against my usage? +
No. Ferry classifies known bots, link previews, automated scans, prefetch requests and duplicate deliveries as excluded traffic at the edge, so they do not consume quota and do not inflate your numbers.
Give every placement its own link.
Create one link per placement, encode it, and watch scans, installs, and revenue land against the code that earned them.