A QR code went out to a customer, scanned perfectly, and installed nothing. The
image was clean and the camera read it first time. What had been encoded was a
web address, and the phone was waiting for a line of text starting
LPA:1$.
That line is the activation code. A QR image, a tap-to-install link and a manual entry screen are three ways of handing the same line to a phone. Most install problems turn out to be problems with the line. This guide covers what the code contains, what to encode, what to send your customer, and what to say when someone reports that a code has already been used.
Encode the activation code, never a URL
The activation code is one line of text, and its format is set by GSMA SGP.22, the Remote SIM Provisioning specification behind consumer eSIM. A complete one looks like this.
LPA: prefix is a QR
wrapper, shown here in grey. The three parts underlined in red are the code
itself, and they are what goes on a manual entry screen.
SM-DP+ is short for Subscription Manager Data Preparation, and the version number is stricter than it looks: SGP.22 says a device "SHALL treat an AC_Format other than '1' as invalid".
Generate the QR image yourself from that string. Two things go wrong otherwise.
Some upstream systems put a hosted web page in the field named for the QR, so
encoding whatever you were handed gives you an image that scans and installs
nothing. That was the failure at the top of this piece, and it is why our own
API points integrators at activation_code and marks the older
qrcode field deprecated.
Case is the second one. SGP.22 requires the matching ID to use "upper case alphanumeric characters (0-9, A-Z) and the '-' in any combination", a choice the spec says "allows more compact alphanumeric QR code encoding". The alphanumeric mode in ISO/IEC 18004 has no lowercase in its character set, so one lowercase character pushes the encoder into byte mode and gives you a denser image for the same string. Pass the code through untouched: no re-wrapping, no case normalising, no whitespace trimming that might eat a trailing field.
Keep the LPA: prefix on the QR, drop it for manual entry
LPA: is a QR wrapper. The activation code itself starts at the
1$. SGP.22 settles this in two consecutive sentences:
When entered manually, the Activation Code SHALL be used as defined above. When provided in a QR code according to ISO/IEC 18004, the Activation Code SHALL be prefixed with "LPA:".
So encode LPA:1$... in the QR, and display 1$...
wherever a customer will type it by hand. On iPhone that screen is Settings,
then Cellular, then Add Cellular Plan, then "Enter Details Manually". The prefix
tells a scanner what sort of payload follows, in the way mailto:
does in a link. LPA is the Local Profile Assistant, which the
Android
Open Source Project describes as "a bridge between the SM-DP+ ... and the
eUICC chip". It runs the download.
Some handsets accept the prefix on the manual screen anyway. Neither Apple nor Google documents that, so do not build on it.
Parse the activation code by field position
The format runs past those three fields, and the optional ones are where parsers break.
| # | Optional field | What it carries |
|---|---|---|
| 4 | SM-DP+ OID | Object identifier from the server's certificate |
| 5 | Confirmation code required flag | 1 when the customer must supply a separate code |
| 6 | CI public key indicator | Added in SGP.22 v3.0 |
| 7 | Delete notification for device change | Only when a profile moves to a new device |
Field four is the SM-DP+ OID, and it is the one most often described wrongly. Among the third-party explanations we read while checking this, it was called a confirmation code more often than an object identifier. A parser written on that reading will show a customer a certificate identifier and ask them to treat it as a PIN.
SGP.22's own examples show what the optional fields do to the shape:
1$SMDP.EXAMPLE.COM$04386-AGYFT-A74Y8-3F815
1$SMDP.EXAMPLE.COM$04386-AGYFT-A74Y8-3F815$$1
1$SMDP.EXAMPLE.COM$04386-AGYFT-A74Y8-3F815$1.3.6.1.4.1.31746$1
$ in the second example above.
Split on $, read fields by index, and expect empty strings in the
middle. Counting the fields you got instead of indexing them is what breaks on
the codes that carry an OID.
Two more rules from the spec will save you trouble. An activation code without a device-change notification is capped at 255 characters. And a device "SHALL ignore a delimiter and any further parameters" beyond the ones SGP.22 defines, so a longer code from a newer server should still work.
Send an install page instead of a platform link
Both platforms take the same shape of link, one hostname apart:
https://esimsetup.apple.com/esim_qrcode_provisioning
?carddata=LPA:1$SMDP.EXAMPLE.COM$04386-AGYFT-A74Y8-3F815
https://esimsetup.android.com/esim_qrcode_provisioning
?carddata=LPA:1$SMDP.EXAMPLE.COM$04386-AGYFT-A74Y8-3F815
A customer taps that on the phone the eSIM is meant for, and the operating system opens its install flow with the profile already identified. No scanning, no second device, nothing typed.
Two caveats before you copy the format. Neither Apple nor Google publishes it,
so treat it as a working convention. And percent-encode the parameter value,
which is what we generate for every eSIM we provision and document as
ios_tap_link and android_tap_link.
Inside carddata sits the activation code with its LPA:
prefix, the QR form of the string. The path is called
esim_qrcode_provisioning and the parameter is called
carddata, which describes the design accurately enough: the link
stands in for a scan.
Everything else about the two links differs.
Apple documents its version. In Set up eSIM on iPhone, Apple says "Your carrier might send you a link instead of a QR code" and puts the requirement at iOS 17.4 or later. The same release added a second useful path: press and hold a QR image inside an email or a web page, then choose Add eSIM, with no second device involved.
Google documents none of it. The AOSP eSIM guide covers QR codes, activation codes and carrier apps. The Pixel help page for setting up an eSIM lists two flows, and a link is not one of them. We found no published URL format, no stated minimum Android version, and no announcement.
The Android link is Google's all the same. The host
esimsetup.android.com serves a Digital Asset Links file handing
every URL on the domain to com.google.android.gms, which is Google
Play services. It resolves to Google infrastructure, and in a desktop browser on
30 August 2026 it answered with a placeholder page titled "eSIM Setup". It is an
ordinary Android App Link with a nearly empty page behind it.
Apple's host works differently. On the day we checked,
esimsetup.apple.com published no A, AAAA or CNAME record and could
not be reached over HTTPS. There is no web server behind Apple's install link.
The URL means something to iOS and to nothing else.
Which is why a platform link is only safe when you already know the platform, and in an email you do not. An iPhone customer who taps the Android link lands on an empty Google page. An Android customer who taps the Apple link gets a DNS error and reads it as a broken product.
Send one page instead. Put the QR, both platform links and the activation code as selectable text on it, and let the customer's device take the path that works. Say on that page that they need Wi-Fi, because Apple lists a connection as a prerequisite for installing an eSIM, with a narrow exception for eSIM-only models in some countries. A page also survives being forwarded to a laptop. An AI assistant reading an eSIM over MCP returns that same address, as we described in Your eSIM Catalog Is Now Agent-Native.
Tell "already used" apart from "failed"
These arrive as the same complaint and need opposite answers. Before releasing a profile, the SM-DP+ checks that the profile for that matching ID is in "'Released' state, or, in case of a retry due to a previous installation failure, in 'Downloaded' state". It counts download attempts, and terminates the order once the maximum is passed.
An install that failed can usually be retried with the same code. An install that succeeded cannot. "It didn't work, let me scan again" and "it worked and now I want it on my new phone" are two different problems, and resending the code only answers the first.
The short version
- Generate the QR from the activation code yourself, character for character.
- Keep
LPA:on the QR, drop it wherever the code is typed. - Split on
$and read by index, allowing empty fields. - Send a page, not a platform link, and mention Wi-Fi on it.
- Separate "already used" from "failed" before you answer the ticket.
If you are building against Zesimo, activation_code,
ios_tap_link, android_tap_link and
install_url all sit on the eSIM object, and the hosted page at
install_url already does everything on that list.
The Zesimo team