Two ways to connect
Account level (start here)
Project level (multi-shop)
Prerequisites
A Shopify store
Owner or admin role on the Hiveku account
A Shopify OAuth app registered in Hiveku settings
client_id + client_secret under Settings → OAuth apps in Hiveku. Subsequent stores reuse the same app. Both the account and project flows require this — the project panel refuses to start OAuth if no provider=shopify app exists yet.Connect at the account level
This is the usual starting point. The first store you connect on an account becomes the account default — every project under that account uses it with no per-project setup.Open the Shopify settings page
/dashboard/commerce/settings/shopify.Click "Connect a store"
- Shop domain — the permanent
*.myshopify.comdomain (not your custom domain). Example:acme.myshopify.com. - OAuth app — picked from the apps you registered in Hiveku settings. If you only have one, it’s preselected.
Authorize on Shopify
https://{shop}.myshopify.com/admin/oauth/authorize. The merchant approves the requested scopes (read_products, write_products, read_orders, read_customers, and more — the full list is in the developer section below).Hiveku finishes the handshake
- Hiveku exchanges the OAuth code for an offline Admin API access token.
- A Storefront API token is minted via the Admin GraphQL API.
- Both tokens are encrypted at rest (
SHOPIFY_TOKEN_ENCRYPTION_KEY, AES-256-GCM). - Webhook subscriptions are registered for the topics Hiveku needs (orders, products, subscription events, GDPR compliance).
- The connection is saved with
purpose = 'account_default'and appears as Active in the settings UI.
?shop= query param against the domain the flow was started for, to defend against shop-substitution attacks.The account-default vs project-override model
Hiveku resolves a project’s Shopify connection with a simple precedence, optimized for agency users: project override → account default → none.Connect (or bind) at the project level
Each project has a Shopify panel with three modes. You’ll find it in two places — they’re the same panel:The three modes
Pick a mode (uses an existing connection)
Open the project's Shopify panel
/dashboard/[projectId]/settings (or the Ecommerce tab). The panel shows the currently effective connection and its source — account default or project override.Choose Inherit, Override, or Disabled
/dashboard/commerce/settings/shopify). Connect at the account level first, then inherit or override here.Connect a project-specific store (new OAuth)
Sometimes the store you want isn’t connected to the account yet and you want it bound directly to this project. The panel’s Connect a project-specific store button runs a fresh OAuth handshake and, on success, binds the result to this project as an override — no separate mode-selection step needed.Click “Connect a project-specific store”
*.myshopify.com shop domain (validated against the acme.myshopify.com shape). The panel uses the account’s registered Shopify OAuth app automatically.Authorize on Shopify
Hiveku binds it to this project
purpose = 'project_override' and the project is switched to Override mode, pointed at the new connection. The panel refreshes to show it as the effective store.Under the hood (for developers)
OAuth routes and intent types
OAuth routes and intent types
intent_type:intent_data.project_id. The shared callback is GET /api/oauth/shopify/callback; it verifies in three layers — signed-state HMAC (CSRF/replay), Shopify callback HMAC (keyed by the app’s client_secret), and a shop-domain match — then exchanges the code, mints tokens, persists, and registers webhooks.Data model + resolver precedence
Data model + resolver precedence
shopify_connections— one row per connected store.purposeis'account_default'or'project_override'.shop_domainis globally unique, so connecting a shop already bound to a different account is rejected.project_shopify_settings— one row per project.override_modeis'inherit' | 'override' | 'disabled'; when'override',shopify_connection_idpoints at the bound connection.
resolveShopifyCredentials({ accountId, projectId }) in src/lib/shopify/credentials.ts applies the precedence: override_mode = 'override' (and a live connection) wins; override_mode = 'disabled' returns none even if an account default exists; otherwise it falls back to the account default (purpose = 'account_default', not disconnected). Results are cached in-process for 60 seconds and busted via bustShopifyCredentialsCache on connect / disconnect / override changes.Panel API surface
Panel API surface
GET /api/builder/shopify/{projectId}/status— the effective connection + the project’s storedoverride_mode/shopify_connection_id.PUT /api/builder/shopify/{projectId}/settings— save a mode (body:override_mode, optionalshopify_connection_id).GET /api/builder/shopify/connections— the account’s connections. Returns only non-secret fields (id,shop_domain,shop_name,purpose,disconnected_at) — never token columns.GET /api/oauth-apps?provider=shopify— the registered Shopify OAuth app(s) used to start OAuth.
Scopes, API version, and tokens
Scopes, API version, and tokens
- Requested scopes live in
DEFAULT_SHOPIFY_SCOPES(src/lib/shopify/auth.ts) — the single source of truth shared by the OAuth start route and the scope-drift cron:read_products,write_products,read_orders,read_customers,write_customers,read_content,write_content,read_themes,write_themes. Your Shopify app’s configured scopes must match. - API version is pinned by
SHOPIFY_API_VERSION = '2025-04'. - The Admin token is offline (long-lived, valid until the app is uninstalled) — no
grant_options[]=per-user, because Hiveku makes server-to-server Admin API calls. - Env vars (builder web service):
SHOPIFY_TOKEN_ENCRYPTION_KEY(32-byte base64, AES-256-GCM for stored tokens) andNEXT_PUBLIC_APP_URL(drives the callback and the/api/webhooks/shopifyendpoint). The OAuth app’sclient_id/client_secretlive per-account inoauth_apps, not in env.
What gets stored
Hiveku stores the minimum needed to operate the integration, on theshopify_connections row:
Multiple stores on one account
You can connect any number of Shopify stores. Only one can be the account default at a time (purpose = 'account_default'); the rest are purpose = 'project_override' and selectable per project via Override mode.
To swap defaults: disconnect the current default, then reconnect the store you want as the new default. Connecting a fresh account default automatically retires the previous active default for that account.
Disconnecting
Disconnect from Hiveku
/dashboard/commerce/settings/shopify. Hiveku marks the row disconnected_at = now(), stops registering webhook events, and clears the credentials from the next deploy. The Admin token is not force-revoked at Shopify — your Shopify admin can also un-install the app to be thorough.(Optional) Uninstall in Shopify admin
app/uninstalled to Hiveku, which sets disconnected_at if it wasn’t already.Reconnecting
Re-running OAuth for a previously connected store (a reconnect —intent_type = 'shopify_reconnect', keyed by the existing connection_id) updates that same connection row in place — encrypted tokens, scopes, shop metadata, and timestamps all refresh, and the revalidation_token is rotated. The connection’s purpose and any project-level bindings survive.