Skip to main content
DeploTeka Set up your first store

By DeploTeka · Last updated November 5, 2026 · 5 min read

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's clientId, secret and appUrl under 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.accessToken exists, a bounded authenticated Shopify Admin API query for currentAppInstallation { app { apiKey } accessScopes { handle } }. Only a matching app.apiKey === row.clientId establishes 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:

FieldMeaning
contractVersionNumber 1
eventId, correlationIdImmutable delivery identities, echoed in the acknowledgement
eventtoken_issued, token_reissued or token_revoked
shop, clientIdShop and dedicated app binding
accessTokenOffline token on issue/reissue; absent on revoke
apiSecretDedicated app secret
grantedScopes, installedAtObserved 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.

  1. Register a shop with the correct bearer; expect200. Missing/wrong bearer must return401. Readback for an unregistered shop must return404.
  2. Read the registered row before token delivery; expect installed:false while client ID, secret fingerprint and app URL remain available. Restart and confirm persistence.
  3. Exercise the authorized acquisition path and durable receiver acknowledgement. Confirm duplicate delivery does not create a second effect and revoke removes usable token state.
  4. 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.
  5. 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.
  6. 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.

Frequently asked questions

Does the generated shim complete extension-only installation?

No. It supplies credential registration and installed-state readback. It does not generate an OAuth flow, a token-delivery receiver or an extension proof collector. An app without those integrations remains pending verification.

Does the shim call Shopify?

Yes. When its stored shop row has an offline access token, installed-state readback makes an authenticated currentAppInstallation query. The returned app.apiKey must match the registered clientId. A stored installed flag or extension ping cannot replace that check.

Can DeploTeka acquire the offline token?

The token_delivery integration uses DeploTeka’s brokered OAuth flow after merchant approval and sends the offline token to the explicitly configured receiver. You must implement the separate /api/deploteka/token contract, persist its receipt and handle revocation. The generated fleet shim does not implement that endpoint.

Is a working token enough to prove our extension is active?

No. Token identity and required extension activation are separate checks. Every required extension needs independent, app-bound evidence. Missing, stale, wrong-app or unknown extension evidence cannot establish a verified installation or update.

Where should the receiver run?

Use a stable HTTPS backend with protected persistent storage, access controls and atomic per-shop updates. The Node scaffold is a starting point; serverless storage and token-delivery handling require integration work.