Skip to content

Open chargers from URLs (expchg:// scheme)

A driver tapping a sticker on the side of a charger should land directly on that charger inside ExpresScan — no manual ID entry, no hunting through a list. This article explains how the app turns a URL into an in-app action, what URL shapes are accepted, and why the indirection through NotificationCenter exists.

ExpresScan listens for two kinds of URLs:

  1. Universal Linkshttps://<host>/... URLs that iOS routes to the app when the matching AASA file is published. These work from Safari, Messages, Mail, and the camera viewfinder.
  2. Custom schemeexpchg://... URLs. Useful for printed stickers and QR codes where a Universal Link would resolve in the browser if the app isn’t installed, and as a deterministic fallback when AASA hasn’t yet propagated to a device.

Both end up at the same place: RootView’s onOpenURL (or onContinueUserActivity for legacy Universal Link delivery). The handler parses the URL into one of four intents, posts a deep link notification, and lets the appropriate subsystem respond.

IntentUniversalCustom scheme
Open a ChargeBoxhttps://<host>/c/<id>expchg://c/<id>
User QR sign-inhttps://&lt;host&gt;/u/<publicId>
Magic link sign-inhttps://&lt;host&gt;/m/&lt;token&gt;
Registration PKCE callbackhttps://&lt;host&gt;/&lt;callback&gt;?code=…

The charger intent (/c/&lt;id&gt;) is the only one that uses the custom scheme today. Sign-in URLs are Universal-Link-only by design: they must be hard for a malicious sticker to spoof, so they require AASA domain ownership.

sequenceDiagram
participant Driver
participant iOS
participant RootView
participant NC as NotificationCenter
participant Scan as ScanCoordinator
Driver->>iOS: Tap sticker / scan QR
iOS->>RootView: onOpenURL(expchg://c/123)
RootView->>RootView: parseChargerDeepLink
RootView->>NC: post chargerDeepLinkRequested
NC->>Scan: handle(chargerId: 123)
Scan-->>Driver: Charger detail screen

The same diagram applies to /u/<publicId> and /m/&lt;token&gt;, except the receiver is the auth pipeline rather than the scan coordinator, and the app must currently be in an unauthenticated state for the notification to fire.

Two URL channels, one parser. Universal Links land in onOpenURL on iOS 18+ and in onContinueUserActivity on older delivery paths. RootView hooks both and routes them through a single handleUniversalLink(_:) so the parsing rules stay in one place.

NotificationCenter as a bus. Parsing happens in RootView because that’s where the URL arrives, but the handlers (ScanCoordinator, the QR sign-in view model, the magic-email view model) live in feature modules. Posting a notification keeps RootView decoupled from those modules and lets us test parsing without standing up the entire app graph.

Custom scheme is a fallback, not the canonical form. Universal Links degrade gracefully — if ExpresScan isn’t installed, the https:// link opens a web page that tells the driver where to get the app. expchg:// URLs just fail. Stickers therefore print the Universal Link as the QR payload; the custom scheme exists for operator tooling and edge cases where AASA hasn’t propagated.

Auth-state gating for sign-in URLs. A /u/<publicId> URL delivered while the driver is already signed in is a no-op: re-running the QR sign-in would clobber the active session. RootView only forwards sign-in notifications when the route is .welcome, .loggingIn, .launching, or .customerSigningIn. Charger deep links, by contrast, are always honored — they’re navigation, not authentication.

  • You can share a charger with another driver by long-pressing the charger ID in the app and copying the link. The recipient taps the link and lands on the same charger.
  • Stickers are tap-to-open. You don’t need to launch ExpresScan first; tapping the NFC area or scanning the QR with the system camera opens the app directly to that charger.
  • If a link doesn’t open the app, AASA hasn’t propagated to your device yet. Force-quitting and re-opening ExpresScan, or installing it fresh, resolves it. The expchg:// form on the same sticker always works as a fallback.
  • Magic links and QR sign-in URLs only work when you’re signed out. This is intentional — if you’ve already got a session, the URL is ignored rather than overwriting it.