| name | violetconnect-bigcommerce |
| description | BigCommerce OAuth, embedded app JWT validation, and pre-registration patterns for VioletConnect |
VioletConnect: BigCommerce Integration
Overview
VioletConnect integrates with BigCommerce via centralized OAuth 2.0 for merchant onboarding. BigCommerce uniquely supports embedded apps that load within the BigCommerce admin iframe using JWT authentication.
Integration Type: Centralized OAuth 2.0 + Embedded App (JWT)
Store Identifier: Store Hash (alphanumeric, e.g., abc123)
Credential Storage: access_token + store_hash (long-lived, no refresh)
Pre-Registration Support: Flows B and C only (no Flow A - BC doesn't support direct install links)
Related Skills:
- violetconnect: Core VioletConnect features and architecture
- violetconnect-patterns: Code patterns (Redux, axiosWrapper, AppContext)
- v-bigcommerce: General BigCommerce API patterns and integration details
BigCommerce OAuth Flow
Standard OAuth Flow (No Pre-Registration)
1. Merchant lands on VioletConnect: connect.violet.io/{appAlias}/platforms/bigcommerce
2. Merchant enters email → Creates session
3. Merchant enters store URL (e.g., "https://store-abc123.mybigcommerce.com")
4. VioletConnect validates store URL format
5. VioletConnect redirects to BigCommerce OAuth:
- URL: https://login.bigcommerce.com/oauth2/authorize
- Params: client_id, scope, redirect_uri, state
6. Merchant approves app in BigCommerce admin
7. BigCommerce redirects back to VioletConnect callback:
- URL: connect.violet.io/{appAlias}/platforms/bigcommerce/callback
- Params: code, scope, context
8. VioletConnect exchanges code for access_token (server-side)
9. VioletConnect creates merchant record with store_hash
10. Merchant completes commission/payout setup
11. Merchant redirected to success page
Key Difference from Shopify: BigCommerce returns a context parameter (format: "stores/{store_hash}") which must be extracted for the merchant record.
BigCommerce Embedded App Flow
Key Feature: BigCommerce supports embedded apps that load within the BigCommerce admin panel as an iframe.
1. Merchant installs app from BigCommerce App Marketplace
2. BigCommerce loads VioletConnect in iframe: /bc-embedded/load
3. BigCommerce sends signed JWT as query parameter: ?signed_payload_jwt={token}
4. VioletConnect validates JWT signature using client_secret
5. JWT contains: store_hash, user.id, user.email, timestamps
6. VioletConnect auto-creates session (no manual login required)
7. Merchant is already authenticated
8. Continue with commission/payout setup
Why Embedded Apps:
- Seamless UX: Merchant never leaves BigCommerce admin
- Auto-Authentication: No need for email entry or manual OAuth
- Single Sign-On: JWT verification provides instant authentication
Use Case: If your channel's app is in the BigCommerce App Marketplace, use embedded app flow for better UX.
OAuth Implementation Details
Dynamic Routing
Page Files:
pages/[appAlias]/platforms/bigcommerce/
├── index.tsx # Store URL entry + OAuth redirect
└── callback.tsx # OAuth callback handler
pages/bc-embedded/
└── load.tsx # Embedded app JWT handler
Key Utility Files:
// utils/bigcommerce/oauth.ts
// OAuth URL construction and token exchange
// utils/bigcommerce/validation.ts
// Store URL validation (*.mybigcommerce.com format)
// utils/bigcommerce/jwt.ts
// JWT validation and decoding for embedded apps
OAuth Parameters
interface BigCommerceOAuthParams {
client_id: string; // BigCommerce app client ID
scope: string; // Permissions (space-separated, e.g., "store_v2_products store_v2_orders")
redirect_uri: string; // VioletConnect callback URL
state: string; // CSRF token + session identifier
response_type: 'code'; // Always 'code' for authorization code flow
context?: string; // Optional: "stores/{hash}" for re-authorization
}
Example OAuth URL:
https://login.bigcommerce.com/oauth2/authorize?client_id=abc123&scope=store_v2_products+store_v2_orders&redirect_uri=https://connect.violet.io/testchannel/platforms/bigcommerce/callback&state=csrf_token&response_type=code
Note: BigCommerce scopes are space-separated (different from Shopify's comma-separated scopes).
Access Token Exchange
After OAuth callback with code, exchange for access_token:
POST https://login.bigcommerce.com/oauth2/token
Content-Type: application/json
Body: {
client_id: string,
client_secret: string,
code: string,
scope: string, // Same scopes from authorize request
grant_type: "authorization_code",
redirect_uri: string,
context: string // From callback params: "stores/{hash}"
}
Response: {
access_token: string, // Store this for API calls
scope: string, // Granted scopes
user: {
id: number,
username: string,
email: string
},
context: string // "stores/{store_hash}"
}
Critical: Extract store_hash from the context field ("stores/{hash}") - this is the primary identifier for the merchant.
Store Hash vs Store URL
Store Hash as Primary Identifier
BigCommerce uses store_hash (not store URL) as the primary identifier for a merchant's store.
Store Hash Format: Alphanumeric string (e.g., "abc123def", "xk4t2p")
Where to Get Store Hash:
- From OAuth response
contextfield:"stores/{store_hash}" - From embedded app JWT
subfield:"{store_hash}" - From store URL:
https://store-{hash}.mybigcommerce.com
Extraction Pattern:
// From context field
function extractStoreHash(context: string): string {
// context format: "stores/abc123def"
return context.replace('stores/', '');
}
// From store URL
function getStoreHashFromUrl(storeUrl: string): string | null {
// URL format: https://store-abc123def.mybigcommerce.com
const match = storeUrl.match(/store-([a-z0-9]+)\.mybigcommerce\.com/i);
return match ? match[1] : null;
}
API Call Pattern
All BigCommerce API calls use store_hash:
const apiUrl = `https://api.bigcommerce.com/stores/${store_hash}/v3/catalog/products`;
const response = await axios.get(apiUrl, {
headers: {
'X-Auth-Token': access_token,
'Content-Type': 'application/json'
}
});
Key Point: Store store_hash in your merchant record, not just the store URL. The store URL can change (custom domains), but store_hash is permanent.
Store URL Validation
// Validate BigCommerce store URL format
function isValidBCUrl(url: string): boolean {
return /^https:\/\/store-[a-z0-9]+\.mybigcommerce\.com$/i.test(url);
}
// Example validation in StoreUrlInput component
<StoreUrlForm
platform="bigcommerce"
placeholder="https://store-abc123.mybigcommerce.com"
validation={isValidBCUrl}
onSubmit={handleStoreUrlSubmit}
errorMessage="Invalid BigCommerce store URL. Must be https://store-{hash}.mybigcommerce.com"
/>
Embedded App JWT Validation
JWT Structure
When BigCommerce loads your app in an iframe, it sends a signed JWT with this structure:
interface BigCommerceJWT {
aud: string; // Your client_id
iss: string; // "bc" (BigCommerce issuer)
iat: number; // Issued at timestamp (Unix epoch)
nbf: number; // Not before timestamp
exp: number; // Expiration timestamp
jti: string; // JWT ID (unique identifier)
sub: string; // Store hash (e.g., "abc123def")
user: {
id: number; // BigCommerce user ID
email: string; // User email
locale: string; // e.g., "en-US"
};
owner: {
id: number; // Store owner user ID
email: string; // Owner email
};
url: string; // Store URL (e.g., "https://store-abc123.mybigcommerce.com")
channel_id: number | null; // Channel ID if multi-channel
}
JWT Validation Pattern
CRITICAL: Always validate the JWT signature before trusting its contents.
import jwt from 'jsonwebtoken';
function validateBCJwt(signedPayload: string, clientSecret: string): BigCommerceJWT {
try {
// Verify signature and decode
const decoded = jwt.verify(signedPayload, clientSecret, {
algorithms: ['HS256', 'HS384', 'HS512']
}) as BigCommerceJWT;
// Verify timestamps
const now = Math.floor(Date.now() / 1000);
if (decoded.exp < now) {
throw new Error('JWT expired');
}
if (decoded.nbf > now) {
throw new Error('JWT not yet valid');
}
return decoded;
} catch (error) {
throw new Error(`JWT validation failed: ${error.message}`);
}
}
Why Validate:
- Prevents JWT tampering
- Ensures request came from BigCommerce
- Verifies timestamp validity
Session Creation from JWT
Once JWT is validated, auto-create a session for the merchant:
// pages/bc-embedded/load.tsx
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { signed_payload_jwt } = req.query;
if (!signed_payload_jwt || typeof signed_payload_jwt !== 'string') {
return res.status(400).json({ error: 'Missing JWT' });
}
try {
// Validate JWT
const decoded = validateBCJwt(signed_payload_jwt, process.env.BIGCOMMERCE_CLIENT_SECRET);
// Extract store_hash from JWT
const storeHash = decoded.sub; // or extract from decoded.url
// Create session
await createSession({
storeHash,
userId: decoded.user.id,
email: decoded.user.email,
isEmbedded: true,
channelId: decoded.channel_id
});
// Redirect to onboarding flow (skip email/store URL entry)
res.redirect(`/${appAlias}/commission`);
} catch (error) {
console.error('JWT validation error:', error);
res.status(401).json({ error: 'Invalid JWT' });
}
}
Benefit: Merchant is auto-authenticated without entering email or going through OAuth manually.
Pre-Registration Integration
BigCommerce supports Flow B (Store URL Lookup) and Flow C (MerchantId Link) only.
No Flow A: BigCommerce does not support direct install links like Shopify. Merchants must go through VioletConnect.
Flow B: Store URL Lookup
- Merchant enters BigCommerce store URL on VioletConnect
- VioletConnect calls
/api/pre-register/lookup?storeUrl={url}&appId={appId} - If found:
- Show
PreRegFoundcomponent - Display merchant name + store URL
- "Connect to BigCommerce" button → redirects to
installLink(OAuth URL)
- Show
- If not found: Continue normal email-first flow
API Endpoint:
// pages/api/pre-register/lookup.ts
GET /api/pre-register/lookup?storeUrl=https://store-abc123.mybigcommerce.com&appId=1
Response (if found): {
merchantId: 123,
merchantName: "Test Store",
storeUrl: "https://store-abc123.mybigcommerce.com",
platform: "bigcommerce",
installLink: "https://login.bigcommerce.com/oauth2/authorize?client_id=...",
status: "pending"
}
Response (if not found): 404
Redux Integration:
// Component usage
const { current, found, loading } = useSelector((state) => state.preRegistration);
useEffect(() => {
if (storeUrl) {
dispatch(lookupByStoreUrl({ storeUrl, appId }));
}
}, [storeUrl]);
if (found && current) {
return <PreRegFound preRegistration={current} onConnect={handleConnect} />;
}
Flow C: MerchantId Link
- Channel sends link:
connect.violet.io/{appAlias}?merchantId={id} - AppContext detects
merchantIdquery param - Dispatches
lookupByMerchantId(merchantId) - If found: Redirect to
/pre-registration/confirm - If not found: Show
PreRegError
AppContext Integration (already handled):
// contexts/AppContext.tsx
const merchantId = router.query.merchantId as string;
useEffect(() => {
if (merchantId && !isNaN(Number(merchantId))) {
dispatch(lookupByMerchantId(Number(merchantId)));
}
}, [merchantId, dispatch]);
Confirmation Page:
// pages/[appAlias]/pre-registration/confirm.tsx
// Shows merchant details, "Connect to BigCommerce" button
// Redirects to installLink on click
BigCommerce Components
StoreUrlInput (BigCommerce)
const bcUrlPattern = /^https:\/\/store-[a-z0-9]+\.mybigcommerce\.com$/i;
<StoreUrlForm
platform="bigcommerce"
placeholder="https://store-abc123.mybigcommerce.com"
validation={bcUrlPattern}
helpText="Enter your BigCommerce store URL"
onSubmit={handleStoreUrlSubmit}
errorMessage="Invalid BigCommerce store URL format"
/>
EmbeddedAppLoader
// pages/bc-embedded/load.tsx
// Handles BigCommerce iframe load with JWT validation
// Auto-creates session and redirects to onboarding flow
OAuthRedirect Component
// Constructs BigCommerce OAuth URL with proper scopes
const oauthUrl = `https://login.bigcommerce.com/oauth2/authorize?${new URLSearchParams({
client_id: clientId,
scope: 'store_v2_products store_v2_orders store_v2_customers',
redirect_uri: callbackUrl,
state: sessionState,
response_type: 'code'
})}`;
// Redirect to BigCommerce OAuth
window.location.href = oauthUrl;
Error Handling
BigCommerce-Specific Errors
| Error | Trigger | User Message | Recovery |
|---|---|---|---|
| Invalid Store URL | URL doesn't match BC format | "Invalid BigCommerce store URL. Must be https://store-{hash}.mybigcommerce.com" | Re-enter URL |
| OAuth Denied | Merchant cancels in BC admin | "You cancelled the connection. Please try again." | Restart OAuth flow |
| Store Hash Mismatch | JWT store_hash doesn't match OAuth context | "Store verification failed. Please try again." | Contact support |
| JWT Expired | JWT timestamp > exp | "Your session expired. Please reload the app." | Refresh page in BC admin |
| JWT Tampering | Signature validation fails | "Security validation failed. Please reload the app." | Contact support |
| Missing Context | OAuth callback missing context param | "Invalid OAuth response from BigCommerce" | Restart OAuth flow |
| Invalid Scopes | Requested scopes not granted | "Required permissions were not granted" | Re-authorize with correct scopes |
Error Component
<BCError
error={error}
onRetry={() => router.push(`/${appAlias}/platforms/bigcommerce`)}
supportUrl={channelConfig.supportUrl}
channelName={channelName}
/>
Testing
Local Setup
Create BigCommerce Sandbox Store:
- Visit: https://www.bigcommerce.com/essentials/free-trial/
- Sign up for a free 15-day trial
- Note your store URL:
https://store-{hash}.mybigcommerce.com
Register App in BigCommerce Developer Portal:
- Visit: https://developer.bigcommerce.com/
- Create new app
- Configure OAuth settings:
- Callback URL:
http://localhost:3001/{appAlias}/platforms/bigcommerce/callback - Embedded app load URL:
http://localhost:3001/bc-embedded/load
- Callback URL:
- Copy
client_idandclient_secret
Set Environment Variables:
# .env.local
BIGCOMMERCE_CLIENT_ID=your_client_id_here
BIGCOMMERCE_CLIENT_SECRET=your_client_secret_here
BIGCOMMERCE_CALLBACK_URL=http://localhost:3001/testchannel/platforms/bigcommerce/callback
BIGCOMMERCE_SCOPES="store_v2_products store_v2_orders store_v2_customers"
BIGCOMMERCE_EMBEDDED_LOAD_URL=http://localhost:3001/bc-embedded/load
- Test OAuth Flow:
- Start VioletConnect:
npm run dev - Navigate to:
http://localhost:3001/testchannel/platforms/bigcommerce - Enter your test store URL
- Complete OAuth authorization
- Start VioletConnect:
E2E Testing
// tests/e2e/bigcommerce-oauth.spec.ts
import { test, expect } from '@playwright/test';
test('BigCommerce OAuth flow with pre-registration', async ({ page }) => {
// Navigate to BigCommerce platform page
await page.goto('http://localhost:3001/testchannel/platforms/bigcommerce');
// Enter store URL
await page.fill('[name="storeUrl"]', 'https://store-test123.mybigcommerce.com');
await page.click('button[type="submit"]');
// Verify pre-registration found
await expect(page.locator('text=Your store is already set up!')).toBeVisible();
await expect(page.locator('text=Test Store')).toBeVisible();
// Click connect button
await page.click('text=Connect to BigCommerce');
// Should redirect to BigCommerce OAuth (external page)
await expect(page).toHaveURL(/login\.bigcommerce\.com/);
});
JWT Validation Tests
// tests/unit/bigcommerce-jwt.test.ts
import { validateBCJwt } from '@/utils/bigcommerce/jwt';
import jwt from 'jsonwebtoken';
describe('BigCommerce JWT Validation', () => {
const clientSecret = 'test_secret';
const storeHash = 'test123';
function createTestJWT(payload: Partial<BigCommerceJWT>): string {
const defaultPayload = {
aud: 'client_id',
iss: 'bc',
iat: Math.floor(Date.now() / 1000),
nbf: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600,
jti: 'unique_id',
sub: storeHash,
user: { id: 1, email: 'test@example.com', locale: 'en-US' },
owner: { id: 1, email: 'owner@example.com' },
url: `https://store-${storeHash}.mybigcommerce.com`,
channel_id: null
};
return jwt.sign({ ...defaultPayload, ...payload }, clientSecret);
}
test('validates valid JWT', () => {
const validJwt = createTestJWT({});
const decoded = validateBCJwt(validJwt, clientSecret);
expect(decoded.sub).toBe(storeHash);
expect(decoded.url).toContain(storeHash);
});
test('rejects expired JWT', () => {
const expiredJwt = createTestJWT({ exp: Math.floor(Date.now() / 1000) - 3600 });
expect(() => validateBCJwt(expiredJwt, clientSecret)).toThrow('JWT expired');
});
test('rejects JWT with invalid signature', () => {
const validJwt = createTestJWT({});
expect(() => validateBCJwt(validJwt, 'wrong_secret')).toThrow('JWT validation failed');
});
test('rejects JWT not yet valid', () => {
const futureJwt = createTestJWT({ nbf: Math.floor(Date.now() / 1000) + 3600 });
expect(() => validateBCJwt(futureJwt, clientSecret)).toThrow('JWT not yet valid');
});
});
Configuration
Environment Variables
# BigCommerce OAuth Credentials
BIGCOMMERCE_CLIENT_ID=abc123def456 # From BC Developer Portal
BIGCOMMERCE_CLIENT_SECRET=your_secret_here # From BC Developer Portal
# OAuth Configuration
BIGCOMMERCE_CALLBACK_URL=https://connect.violet.io/{appAlias}/platforms/bigcommerce/callback
BIGCOMMERCE_SCOPES="store_v2_products store_v2_orders store_v2_customers"
# Embedded App Configuration
BIGCOMMERCE_EMBEDDED_LOAD_URL=https://connect.violet.io/bc-embedded/load
Scope Format: Space-separated (NOT comma-separated like Shopify)
Common Scopes:
store_v2_products- Read/write productsstore_v2_orders- Read/write ordersstore_v2_customers- Read/write customersstore_v2_content- Read/write contentstore_v2_information- Read store information
Channel-Specific Config
interface ChannelConfig {
bigcommerceClientId: string;
bigcommerceClientSecret: string;
bigcommerceScopes: string; // Space-separated scopes
supportsBCEmbedded: boolean; // Enable embedded app flow?
bigcommerceEmbeddedLoadUrl?: string; // Embedded app load URL
}
Example:
const channelConfig: ChannelConfig = {
bigcommerceClientId: process.env.BIGCOMMERCE_CLIENT_ID,
bigcommerceClientSecret: process.env.BIGCOMMERCE_CLIENT_SECRET,
bigcommerceScopes: 'store_v2_products store_v2_orders',
supportsBCEmbedded: true, // Enable iframe loading
bigcommerceEmbeddedLoadUrl: process.env.BIGCOMMERCE_EMBEDDED_LOAD_URL
};
Related Documentation
System Docs
- BigCommerce Integration:
prism-brain/systems/violet-connect/bigcommerce-integration.md - OAuth Patterns:
prism-brain/systems/violet-connect/oauth-patterns.md - Embedded App Architecture:
prism-brain/systems/violet-connect/embedded-apps.md
Code Patterns
- violetconnect: Core features (subdomain routing, session management, 13-step wizard)
- violetconnect-patterns: Redux, axiosWrapper, AppContext, API proxy patterns
Platform Integration
- v-bigcommerce: BigCommerce API patterns (products, orders, webhooks, V2 vs V3 APIs)
Implementation Guides
- Pre-Registration Implementation:
prism-brain/product/specs/merchant-pre-registration/guides/violet-connect/ - BigCommerce PR Guides:
prism-brain/product/specs/merchant-pre-registration/guides/violet-connect/pr-*-bigcommerce.md