Skip to content
All articles
9 min read

Android Package Name Fragmentation: One App Is Not One Package

A single app icon splitting into several labeled Android package identifiers

You key your Android open-app rule to com.instagram.android, ship it, and Instagram opens for everyone who has it installed. You do the same thing for Telegram, key it to org.telegram.messenger, and a chunk of your Telegram traffic still lands on a browser fallback — visitors who swear they have the app, because they do. The rule isn’t broken. The package string is incomplete.

An Android package name is not the same thing as “the app.” It’s a build identifier, and several popular apps ship more than one build, each under its own package id. If your routing, your ad platform’s device targeting, or your own QA checklist assumes one app equals one package, you have a blind spot sized exactly to however many of your visitors installed the other build.

Where the assumption breaks

AppPackages
Telegramorg.telegram.messenger (Play), org.telegram.messenger.web (the APK on telegram.org), org.thunderdog.challegram (Telegram X)
WhatsAppcom.whatsapp, com.whatsapp.w4b (Business)
TikTokcom.zhiliaoapp.musically, com.ss.android.ugc.trill
Instagramcom.instagram.android
Shopeeone package per country: com.shopee. + id, br, vn, th, ph, tw, my, sg, mx, cl, ar
Amazoncom.amazon.mShop.android.shopping globally, except India: in.amazon.mShop.android.shopping

Instagram is the outlier in this table — one app, one package, no exceptions. Everything else on it is a reminder that “the package name” is a question with more than one right answer, and the answer depends on where the visitor actually got the app.

None of this shows up as an error anywhere in your stack. A visitor with the “wrong” package installed doesn’t trigger a failed request or a logged exception — they just fail to match a rule that was written for a different build of the same app, and fall through to whatever your default does next. If that default is a browser fallback or a store page, the visitor sees exactly what someone with no app installed would see, and nothing in your logs distinguishes the two cases from each other.

Telegram: three packages, one product

Telegram is the case nobody expects, because Telegram is the app every media buyer assumes they already know. Three separate package ids resolve to three separate installs of what is, from the visitor’s side, the same product:

  • org.telegram.messenger — the Play Store build, the one most targeting rules default to.
  • org.telegram.messenger.web — the APK Telegram distributes from its own website, telegram.org. This isn’t a guess or a community mirror’s naming choice: the package id is read directly out of that APK’s manifest, package=org.telegram.messenger.web, versionName 12.10.0. Telegram itself ships this build under a different identity than its Play build.
  • org.thunderdog.challegram — Telegram X, a separate client entirely, with its own package.

A rule that targets only org.telegram.messenger is correct for every visitor who used the Play Store. It is silently wrong for everyone who downloaded straight from telegram.org — a real, non-trivial acquisition path in regions where Play access is inconsistent or where visitors default to the vendor’s own site. Those visitors have Telegram open on their phone in front of them, and your routing sends them to a fallback that tells them to go install something they already have.

There’s no way to tell, before the click, which of the three a given visitor is running. Nothing about the referring source, the device, or the click itself carries that information — the only way the routing layer finds out is by asking Android directly, one package at a time. That’s the entire justification for treating this as a list problem instead of a single-field problem: you’re not choosing which Telegram package is “correct,” you’re covering the three that are all correct for someone.

WhatsApp: consumer and Business are different packages

com.whatsapp is the consumer app. com.whatsapp.w4b is WhatsApp Business — a separate install, separate package, common among the merchants and small operators a lot of affiliate funnels are built to reach. If your audience skews toward business accounts and your rule only checks com.whatsapp, you’re testing for the wrong half of your own traffic.

The two apps don’t merge on a device that has both — many small business owners run consumer WhatsApp for personal contacts and Business for their storefront, on the same phone, at the same time. A rule that stops at the first match still needs to decide which of the two to check first, and that decision should follow your audience, not a default written before anyone thought about which WhatsApp a merchant actually opens links in.

TikTok: two packages, no visible difference to the visitor

com.zhiliaoapp.musically and com.ss.android.ugc.trill are both TikTok — the same app on the other end of every TikTok ad click you’re already tracking. Nothing in the app’s UI tells a visitor which build they’re running, and nothing forces them to pick one — which build lands on a device depends on how and where it was installed. A rule that checks one and not the other will pass some visitors and silently miss others who are, as far as they know, using the same app.

Shopee: eleven packages, no shared one

Shopee doesn’t have a single Android package at all. It has one per country — com.shopee.id, com.shopee.br, com.shopee.vn, com.shopee.th, com.shopee.ph, com.shopee.tw, com.shopee.my, com.shopee.sg, com.shopee.mx, com.shopee.cl, com.shopee.ar. There is no com.shopee that catches all of them. If you run traffic across more than one of Shopee’s eleven markets, “the Shopee package” isn’t a question with a single answer — it’s a lookup keyed on which country app the visitor actually has.

Amazon: one package everywhere, with one exception

Amazon is close to the simple case: com.amazon.mShop.android.shopping is the package almost everywhere Amazon operates. India is the one carve-out, with its own in.amazon.mShop.android.shopping. Everywhere else — including Japan, where a jp.amazon.mShop.android package shows up on APK mirror sites — the global package is what’s actually live on Play. That Japanese-looking package id is not a real Play listing; treating it as one adds a candidate that will never resolve to an install.

This is worth stating plainly because it runs the opposite direction from every other row in the table: most of the fragmentation here is real apps you should be targeting and currently aren’t. The Amazon case is the reminder that not every package-shaped string you find is one — a plausible id sitting on a mirror site costs you nothing to add to a candidate list and returns nothing when you do, but it’s worth confirming a package actually resolves on Play before it goes into a rule, rather than trusting that it looks right.

The target is a list, not a string

Android’s own jump mechanism only accepts one package per attempt — a shortcut iOS doesn’t get:

intent://host/path#Intent;scheme=https;package=<package>;end

To cover an app that ships under more than one package, you don’t get to put two ids into one package= field — you try candidates in sequence, one intent per package, until one resolves to an installed app. This has one hard rule attached to it: no candidate in that sequence may carry a S.browser_fallback_url, including the last one. A fallback on the first attempt lets a visitor who simply has the second package installed get pushed into the browser before that second attempt is ever tried — which looks exactly like “app not installed” from your side, and is actually “wrong package tried first.” The fallback belongs after every candidate is spent, applied by your own page, not attached to any single step.

Which means the real object you’re maintaining per app isn’t a package name. It’s an ordered list of package candidates, and the order is a decision: whichever candidate you put first is the one that wins when a visitor happens to have more than one build, and is the only one that gets tried at all for a visitor whose Chrome navigation didn’t carry a user gesture — Chrome won’t launch an intent:// without one, and silently falls through to the fallback instead. Put the highest-probability build first for your actual traffic mix — for most funnels that’s the Play package — but “most funnels” is exactly the assumption Telegram’s website APK breaks, and Shopee’s eleven-way split breaks completely, since there is no single highest-probability country to default to across markets.

For Telegram, that candidate list is three attempts, tried in order, each with no fallback attached:

intent://resolve?domain=<x>#Intent;scheme=https;package=org.telegram.messenger;end
intent://resolve?domain=<x>#Intent;scheme=https;package=org.telegram.messenger.web;end
intent://resolve?domain=<x>#Intent;scheme=https;package=org.thunderdog.challegram;end

Only after all three have been tried and none resolved does your own page decide what a visitor with no Telegram at all sees. Skip the second line because it “isn’t the real package,” and you’ve quietly rebuilt the exact blind spot this article opened with.

Store fallbacks inherit the same problem

The generic Android store fallback is a template:

https://play.google.com/store/apps/details?id=<package>

Fill it with the wrong candidate and you’ve just told a visitor who already has the app to go “install” it again — or worse, for Shopee, sent them to one specific country’s listing while your traffic spans eleven of them. There is no com.shopee store page to fall back to; filling the template with any single Shopee package sends ten of eleven markets to the wrong country’s listing. For a split-package app without a shared identity, a store fallback isn’t a safety net, it’s a second place the same wrong-candidate problem can resurface. The web page you already control is correct everywhere the store link can’t be.

What this changes about how you build the rule

Treat every app in your routing config as a candidate list, not a single field, and treat the order of that list as a real setting you tune per traffic source rather than a default you never revisit. For Telegram specifically, that means deciding whether org.telegram.messenger.web belongs in the list at all — and for most funnels running any meaningful volume, it does, because leaving it out doesn’t fail loudly. It just quietly stops counting people who already did what you wanted them to do.

The audit worth running before you trust any Android app-open number: for every app in your rule sheet, list every package id that app is known to ship under, check each one against your current rule, and treat every app with only one candidate as a rule you haven’t finished writing yet — not as a rule that’s simpler because the app happens to be simpler.

A tracker that models this correctly stores a package list per app, in a set order, rather than a single id field — which is the shape DarkCore PWA Tracker uses for Android routing, precisely because “one app, one package” stopped being a safe assumption the moment Telegram shipped a second one.

  • android package name
  • org.telegram.messenger
  • whatsapp business package name
  • tiktok package name
  • app package id