Skip to main content

Shopify OAuth Pairing - How the App Store Flow Works

Deep dive on the ERPClaw Shopify OAuth pairing flow. Six-character codes, single-use redemption, HKDF-derived per-shop HMAC, App Bridge v4 session tokens. Token never persists on our infrastructure.

This page is for merchants and reviewers who want to understand exactly how the Shopify App Store install flow works, what gets stored where, and why the design eliminates a common class of security incidents.

The five-second version

When you click Add app in the Shopify App Store, three things happen in order:

  1. Shopify and our Worker complete an OAuth handshake (offline access token issued, HMAC verified, scopes accepted).
  2. Our Worker mints a six-character pairing code and shows it to you in the embedded admin UI.
  3. You run erpclaw shopify-connect --pairing-code ABC-X9Z on your own machine. The code is redeemed, the OAuth token is forwarded to your ERPClaw, and the Worker deletes the token within 60 seconds.

After that, your ERPClaw talks to Shopify directly. The Worker is no longer in the data path.

Why pairing codes instead of polling

The most common pattern for OAuth + self-hosted clients is “tell the client to come pick up the token via OAuth callback URL.” That breaks when the client is behind NAT, has no inbound HTTPS, or is a CLI tool without a web server.

ERPClaw uses out-of-band pairing: the user copies a short code from one screen and pastes it into another. Same UX as Discord bot linking, GitHub CLI device flow, or Apple TV sign-in. Works regardless of the client’s network topology.

The code:

  • Six characters from the unambiguous alphabet ABCDEFGHJKLMNPQRSTUVWXYZ23456789 (no 0/O/1/I)
  • Single use
  • Ten-minute TTL (Cloudflare KV expiration)
  • Cryptographically random (32 bits of entropy; sufficient for a 10-min single-use exchange)

If a code is leaked or guessed, the worst case is the attacker pairs to their own ERPClaw instead of yours. The token still gets deleted from our Worker. Your store is unaffected.

What lives on our Worker (and for how long)

ItemLocationLifetime
Six-character pairing codeCloudflare KV pair:{code}10 minutes max
OAuth offline access tokenCloudflare KV pair:{code}Deleted on first redemption (typically <60s)
Per-shop HMAC secretCloudflare KV pair:{code}Deleted with token; merchant’s ERPClaw retains it
Shop domainCloudflare KV meta:{shop}Until uninstall + 30 days
Owner emailCloudflare KV meta:{shop}Until uninstall + 30 days
Status blobCloudflare KV status:{shop}30-day rolling

Notably not on our Worker:

  • Customer data (names, emails, addresses)
  • Order data (amounts, line items, products)
  • Product data (SKUs, prices, inventory)
  • General ledger entries
  • Any financial total in any form

Those live on your ERPClaw, full stop.

App Bridge v4 session tokens

The embedded admin UI talks to our Worker using App Bridge v4 session tokens (getSessionToken()). Each token is a JWT signed by Shopify with the shop’s session secret. Our Worker:

  • Verifies the JWT signature against the Shopify session-secret API
  • Validates the aud, iss, and exp claims
  • Caches verified tokens for up to 60 seconds (within the JWT’s exp window)
  • Rate-limits per-shop requests

If you are integrating against our Worker directly (advanced use), see the JWT verification source at github.com/avansaber/erpclaw-addons.

Per-shop HMAC secret derivation

Every shop has a unique HMAC secret used to sign status pushes from your ERPClaw to our Worker. The secret is derived once via HKDF:

shop_secret = HKDF-SHA256(
  ikm:    STATUS_HMAC_MASTER_KEY,    // 32-byte secret, only on Worker
  salt:   shop,                       // e.g. "my-store.myshopify.com"
  info:   "erpclaw-status-v1",
  length: 32 bytes
)

The master key never leaves our Worker. Each shop’s derived secret is known only to our Worker and to the merchant’s ERPClaw (delivered once during pairing).

If we ever need to rotate the master key, every existing shop’s derived secret rotates with it. Your ERPClaw will start getting 401s on status pushes until you re-pair. We do not plan to rotate without notice.

What happens on uninstall

Two webhooks fire when you uninstall the app:

  1. app/uninstalled (immediate). Worker drops meta:{shop}, status:{shop}, and any pending command queues. The next status push from your ERPClaw will be rejected because there is no shop record to validate against.
  2. shop/redact (48 hours later). Worker double-checks all *:{shop} keys are cleared.

Your local ERPClaw data is not deleted. GL entries, orders, customers, and products remain in your SQLite database. If you want to delete them, do that locally; we have no remote way to reach into your machine.

Custom-app flow (skipping the Worker)

If you would rather not use the App Store flow at all, ERPClaw supports the Shopify Custom App pattern. See the App Store vs Custom App page for the comparison.

In short: create a Custom App in your Shopify Partners dashboard, grab the shpat_ access token, and run:

erpclaw shopify-add-account \
  --company-id 1 \
  --shop-domain my-store.myshopify.com \
  --access-token shpat_xxxxx

No Worker, no pairing code, no OAuth. You manage the token rotation yourself.

Source