Extension-only Shopify apps: receiver, offline token and installation proof
The generated fleet shim is a registration and readback starter. It is not a complete extension-only installation service. A verified installation requires a usable offline token for the exact dedicated app and independent evidence for each required extension.
A theme extension, web pixel, checkout UI extension or Function can have no existing backend. Fleet registration, token custody and verification still need a backend. The scaffold provides two routes; you must integrate token acquisition and extension proof before relying on a complete installation state.
What the scaffold does
Run the documented CLI command in a repository containing shopify.app.toml and extensions/:
npx deploteka onboard . --scaffold
The CLI writes deploteka-fleet/fleet.mjs and a README without overwriting an existing file. The generated file runs on Node and provides:
POST /api/fleet/register: authenticate the registration bearer and persist the dedicated app'sclientId,secretandappUrlunder its shop domain.GET /api/fleet/installed?shop=…: authenticate the same bearer, read that row, and return registration facts plus observed installation state.- If
row.accessTokenexists, a bounded authenticated Shopify Admin API query forcurrentAppInstallation { app { apiKey } accessScopes { handle } }. Only a matchingapp.apiKey === row.clientIdestablishes the token's app identity. Observed scopes come from Shopify.
The scaffold does not generate OAuth callbacks, the token-delivery endpoint below, or a required-extension collector. Installing it alone does not make an extension-only app ready for verified onboarding.
Acquire the offline token through an authorized integration
Use one of these existing integration paths. A merchant must approve the correct dedicated app; deployment or credential registration is not merchant consent.
DeploTeka token delivery
For a token_delivery app, DeploTeka owns the brokered OAuth callback and exchanges the merchant-approved authorization for an offline token. Configure the app card's receiver base URL and private registration bearer, then explicitly authorize the receiver binding for that app. A public marketing URL is not a receiver and there is no fallback destination.
Implement POST /api/deploteka/token on that configured base URL. This is a different endpoint from /api/fleet/register; sending its payload to the fleet registration route does not store an offline token.
The generic contract sends:
| Field | Meaning |
|---|---|
contractVersion | Number 1 |
eventId, correlationId | Immutable delivery identities, echoed in the acknowledgement |
event | token_issued, token_reissued or token_revoked |
shop, clientId | Shop and dedicated app binding |
accessToken | Offline token on issue/reissue; absent on revoke |
apiSecret | Dedicated app secret |
grantedScopes, installedAt | Observed scopes and installation time, when known |
Authenticate the bearer before processing. Bind the configured receiver to its tenant and app, and verify the incoming shop/client identity against the registered row. Deduplicate using the idempotency-key header and event identity. Persist the credential change and durable delivery receipt atomically before acknowledging success. Replays must return the original receipt; a revoked generation must not be restored by a stale replay.
For the Node starter, integrate that durable store with readStore: the correct shop row must expose its current offline token as accessToken, with the registered clientId, secret and appUrl preserved. Do not log the token, bearer, secret or payload. On token_revoked, remove the usable token and persist revocation before acknowledging it. A production shared receiver needs tenant/app isolation; a global shop-only JSON map is not sufficient for multiple tenants sharing a shop.
Return JSON with contractVersion: 1, matching eventId and correlationId, status: "applied" (or "replayed"), an opaque receiverOperationRef and ackSchemaHash in the form sha256:<64 lowercase hex>. A generic HTTP200 is not a durable acknowledgement. Implement the authenticated status endpoint agreed with the operator for unknown outcomes; without it, reconciliation remains limited. The app card links the complete normative contract at docs/project/contracts/TOKEN-DELIVERY-CONTRACT-v1.md in the repository.
After merchant approval, distinguish delivered from verified. Read back installed state using the delivered offline token; keep missing-token, denied, malformed, wrong-app and unavailable-provider results unverified. Do not ask a merchant to reinstall merely because the receiver needs repair.
Existing app OAuth
If your app already owns OAuth, resolve credentials per shop and persist the resulting offline session for that dedicated client. Expose that session to the fleet runtime or the scaffold's store. An online session, an installation webhook flag or an extension ping is not a substitute for an offline token whose authenticated app matches the registration.
Prove each required extension separately
The fleet installed boolean proves offline-token app identity, not all extension requirements. The control plane also checks required extensions before accepting the complete installation. Declare the required handles for the app and provide independent evidence bound to the same client and intended release.
The SDK's extensionAttestation callback is an integration seam for a collector; the generated Node shim does not implement that collector. Its basic envelope contains contractVersion: 1, clientId, observedAt and per-handle verdicts active, absent or unknown. Build/update verification additionally needs the exact intended module/build identity and runtime observation required by the release verifier. Do not manufacture an active verdict from registration, deploy exit status, configured scopes, a stored flag or an unauthenticated ping.
Missing or unsupported collection remains unknown. Wrong-client, stale, missing-handle and mismatched-build evidence cannot become a verified update. Until your receiver and required extension collector are implemented and exercised, this topology has a scaffold available, not a complete verified setup.
Host and configure the receiver
Use stable HTTPS and protected persistent storage. Mount a persistent volume for a single-tenant Node prototype; use a tenant-aware transactional store for shared production hosting. A serverless port needs storage and concurrency work, not just a URL change.
FLEET_REGISTER_TOKEN private bearer for both fleet routes; unset rejects requests
FLEET_STORE_PATH persistent credential replica path for the Node prototype
PORT listening port, default 8787
Token delivery uses the explicitly configured app-card receiver bearer. Keep its authorization and durable receipts separate from public extension traffic. Configure the reachable receiver base on the app card and review the exact receiver binding when granting delivery authority.
Verification checklist
Use synthetic data in a local fixture first. Never paste production credentials into terminal transcripts.
- Register a shop with the correct bearer; expect200. Missing/wrong bearer must return401. Readback for an unregistered shop must return404.
- Read the registered row before token delivery; expect
installed:falsewhile client ID, secret fingerprint and app URL remain available. Restart and confirm persistence. - Exercise the authorized acquisition path and durable receiver acknowledgement. Confirm duplicate delivery does not create a second effect and revoke removes usable token state.
- Read back with no token, an online-only session, wrong-app token and denied/malformed provider response; none may report a verified installation. A matching offline token must use Shopify's observed app identity and scopes.
- With a valid token but missing required extension proof, confirm the complete installation remains pending. Then exercise the collector for the intended app, handles and build. Wrong app/build, absent and unknown verdicts must remain unverified.
- Change the extension build while leaving configuration unchanged; verify the intended new build and runtime observation. Repeat only after exact proof, then check that no extra write occurs.
These checks establish the receiver and proof contract. They do not claim that a new customer has completed a real merchant-approved installation; that requires the authorized end-to-end run for your app.