Skip to content

Push app updates to your customers

When you ship a new version of your protected app, PyLocket can notify every customer on their next app launch — they'll see a popup offering to install the new version. No more emailing customers asking them to redownload manually, no more stale security patches, no more "which version are you running?" support threads.

This guide walks through how it works and how to push your first update.


How it works

You click "Push latest version" in the portal
   ↓
End-user launches your protected app
   ↓
App silently calls PyLocket to check for updates
   ↓
PyLocket says: "version X.Y.Z is available" + signed manifest
   ↓
App verifies the signature, then shows a popup:
   [ Install now ] [ Not right now ] [ Skip this version ]
   ↓
If Install: downloads new version, verifies it, swaps it into place,
            and relaunches as the new version.
            Old version kept on disk with a .old.<timestamp> suffix.

The check runs in a background thread so your app never stalls at launch waiting for PyLocket — if our servers are slow or offline, your app launches normally and picks up the update on the next launch.

PyLocket signs every update manifest, and the manifest names the SHA-256 of the build to download. The end-user's protected app refuses an update whose manifest signature or file hash doesn't match, so a network attacker can't swap in a malicious binary. That signature protects the update channel; it is not a code signature on your app. The update delivers the bytes your build serves, so on Windows and macOS sign the build and upload the signed copy (Replace with Signed Package) before you push it.


Before you start

  • Your app must be protected with PyLocket engine v1.0.0.0.0.178 or newer. To enable updates, re-protect and re-ship once; future updates after that flow normally. Your customers don't need to do anything special, and the first re-protected build they install is the last manual install they'll ever need.

    If you pushed an update before September 2026 and nothing happened, that was our fault

    Builds protected on engines v76 through v177 carry the update-check code, and the server really did send them a signed update. But a defect on our side meant the popup never appeared for anyone: the check ran, failed to verify the update silently, and gave up without telling you or your customer. Your push worked; the app could not act on it.

    This is fixed in v178. There is no way to repair an app that has already shipped, because the broken code is the update mechanism itself — upload a fresh build on v178 or later and have your customers download it once. Every push after that reaches them normally.

    • You need an active Pro subscription (pushing updates is a Pro feature).
    • At least one READY build for the app (a released one if the app serves only released builds; see below), plus one active license holder to push to.

Push an update to everyone

Bulk push — the most common flow.

  1. Ship your new protected build through PyLocket as usual. Upload it, wait for status READY. On Windows and macOS, sign it with your own certificate and upload the signed copy with Replace with Signed Package before you push.
  2. Open your App in the portal: Apps → [your app].
  3. Scroll to the Push app update section. You'll see:
  4. Latest READY build — the build PyLocket will push (Latest released build if your app serves only released builds, below).
  5. Push latest version button.
  6. Click Push latest version. A modal appears:
  7. Its title names the version PyLocket will push, and it shows how many active devices are already on it.
  8. Optionally type release notes (plain text, shown verbatim in the end-user popup — e.g. "Security fix for login flow").
  9. Click Push to all eligible users.
  10. The section refreshes to show an Active push card with the target version, timestamp, release notes, and an adoption breakdown that fills in as customers launch their apps over the next hours/days.

That's it. Every customer's next app launch will show the popup.

Watching adoption

The adoption bar under the Active push card shows:

  • Installed (green) — devices that installed this update.
  • Skipped (amber) — devices where the end-user clicked Skip this version. They won't be prompted again until you push a newer version than this one.
  • Pending (grey) — offline, haven't launched since the push, or haven't responded yet.

Give it time — an end-user who hasn't opened your app in a week will be a Pending device until they do.

Clearing an active push

If you pushed by mistake, or want to stop prompting customers:

  • App detail → Push app update section → Clear active push (red link on the Active push card).

End-user apps stop prompting on their next launch. Customers who already installed keep the new version. Customers who clicked Skip stay on the old version. No rollback is forced.

Deleting the build a push names (on the app's Builds page, or DELETE /v1/apps/{app_id}/builds/{build_id}) clears that push too, and every per-customer push to that build: a deleted build can never be offered again. A customer whose own push is cleared this way gets your app's push, as when you clear it by hand.

If your app serves only released builds

An app whose delivery mode is released_only gives customers only the builds you released. In the portal, release a build on its app's Builds page (Release on the build's row), and switch the app on its page under End-user delivery (Serve released builds only…). The default choice releases the build each platform serves now and the build of your active push, so each platform keeps serving the same build. It releases nothing else: a link your customers hold for an older build (a redeemed-code download, a direct or trial link) stops working at the switch unless you release that build first (see Links already issued). Updates follow the same rule:

  • A push that names no build (the portal's Push latest version sends none) pushes the newest released build. The portal's Push app update section shows it as Latest released build, and the push dialog names the version PyLocket will push before you confirm.
  • A push that names a build (target_build_id) must name a released one, otherwise it is refused with 409 and "reason": "build_not_released".
  • You cannot unrelease the build of the active push (409, "reason": "active_push_target"): clear the push first.
  • Switching an app to released_only while its active push names an unreleased build is refused (409, "reason": "push_target_not_released") unless you send "release_current": true, which releases that build too.

A push to one customer (below) may still name an unreleased build, for a beta.

If your app has builds for more than one operating system

An app has one push target: the build you push (by default the newest ready build, whatever its operating system). Only devices running that build's operating system are offered it. Devices on your app's other operating systems keep their version and are not prompted: a macOS build cannot run on Windows, so PyLocket does not offer it there. In the adoption bar those devices stay Pending, and the push is not marked complete while they remain.

The portal cannot choose which build to push: Push latest version always pushes the newest ready build (the newest released one if your app serves only released builds), whatever its operating system. To update customers on another operating system, wait until this rollout is done, then push that system's build through the API: POST /v1/apps/{app_id}/push-update with "target_build_id" set to that build's id. A push replaces the active push. A build older than the active target is refused with 409 until you clear the push (Clear active push, or DELETE /v1/apps/{app_id}/push-update).

PyLocket decides by the platform recorded on each build. A build whose platform is not one operating system (for example cross_platform) is offered to every device, as before.


Push an update to one customer (targeted)

Useful for beta testing — push a new version to a single customer before you bulk-push to everyone else.

  1. Go to Licenses.
  2. Find the customer's license row. On the right, click the cloud-arrow icon (tooltip: "Push app update to this customer's devices").
  3. The dialog names the build it will push: your newest READY build, or your newest released build if the app serves only released builds. Optional release notes → Push update.

That one license's devices get the push on their next launch; all other licenses keep seeing whatever the app-level push says (or no push at all). When you're happy with the beta, do the bulk push — it'll override any stale per-license pushes automatically.


What the end user sees

A standard system dialog, drawn by your app's own process — it looks native on every platform:

┌────────────────────────────────────────────┐
│                                            │
│   A new version is available               │
│                                            │
│   Version 1.2.0                            │
│                                            │
│   Security fix for login flow              │
│                                            │
│   [ Install now ]  [ Not right now ]       │
│                    [ Skip this version ]   │
│                                            │
└────────────────────────────────────────────┘
  • Install now — downloads, verifies, installs, relaunches. Takes ~5–30 seconds depending on download size.
  • Not right now — dismisses. Same popup appears on the next launch.
  • Skip this version — dismisses permanently for this version. They won't be prompted again until you push a newer version. PyLocket shows the popup again automatically when the target changes.

If the user closes the popup via the window's X button, it's treated as Not right now — they'll see it again next launch rather than getting bombarded.

What happens on install

  1. Download: your new protected build streams to a local cache directory with an inline SHA256 check. If the hash doesn't match, the install aborts before touching anything.
  2. Rename — the running binary gets renamed to <name>.old.<timestamp> next to the original location. The rename is legal even while the binary is running (the OS tracks open files by inode/handle, not path). On Windows, PyLocket uses a 3× retry with exponential backoff to absorb transient sharing-violations from AV scanners.
  3. Move — the new binary moves into the original path.
  4. Relaunch — PyLocket spawns the new binary with the same command-line args your customer used, then exits the old process. The switchover is nearly instant — the customer sees the app re-appear as the new version.

The old binary stays on disk with a timestamped .old suffix so customers can always revert if the new version breaks something — they can manually run MyApp.old.20260420-120305 and continue as before.


Platform-specific details

All three platforms use the same four-step primitive (download → verify → rename-then-replace → relaunch), differing only in what gets renamed:

Platform What's renamed Old-binary location
Linux Single ELF binary /path/to/myapp.old.20260420-120305
macOS The whole .app directory /Applications/My.app.old.20260420-120305/
Windows (one-file build) Single .exe C:\...\myapp.exe.old.20260420-120305
Windows (one-folder build / CxFreeze / Briefcase) The whole install directory C:\Apps\MyApp.old.20260420-120305\

Troubleshooting

The update popup isn't showing up on my test device.

Most common cause: your customer's app was protected with an older engine version (pre-v1.0.0.0.0.76) that didn't include the update-check code. Re-protect and re-ship the app once; all updates after that will flow normally.

Second most common cause: the device's license is expired or revoked. PyLocket doesn't push updates to inactive licenses. Check the license's Status column on the Licenses page.

The customer installed the update but their version in the Licenses table didn't change.

The installed version is recorded on the device's next app launch after install. If they just installed and haven't yet re-launched, the table is stale — refresh after 30 seconds.

The customer says the install failed.

  • On Windows, aggressive antivirus software can hold locks on the running binary that defeat even PyLocket's retry loop. The customer can usually fix it by exiting the app fully (including tray/menubar icons) and re-launching — the next launch will try the install again. If the problem persists, they should allowlist your app in their AV.
  • On any platform, a corrupted download (sha256 mismatch) is caught before any file is touched — the old binary is left intact and the customer can retry later.
  • The old binary is preserved with a .old.<ts> suffix, so nothing is ever destroyed. If the install left things in a weird state, the customer can rename the .old file back and resume.

How do I roll back a push?

Clear the active push via App detail → Push app update → Clear active push. That stops prompting new devices. Customers who already installed the new version stay on it — there's no server-side rollback of installs. If you need a forced downgrade, push a fresh build that happens to be v(old) instead of v(new) — the end-user popup treats it as a new update and customers opt in to install it.

Do customers who've never launched since a push eventually see the prompt?

Yes. The push target sits on your app's record indefinitely. Whenever a customer's license holder finally launches, their app checks with PyLocket, sees there's a target, and shows the popup. No time limit.


Privacy and security

  • Nothing about your end-user's identity is sent to PyLocket beyond the license key and a device fingerprint hash. The fingerprint is a SHA-256 of a few stable system identifiers (MAC address, hostname, OS user) — not reversible to PII.
  • The update check is the only regular outbound call the protected app makes after initial activation. It's a plain HTTPS POST with a short body; failures are silently ignored so the app always launches even if our servers are down.
  • Signed manifests prevent network-level tampering. Every update-available response carries a cryptographic signature over the manifest. The end-user's app refuses to install any download whose manifest signature or SHA-256 fails verification.

See also