Mercentia uses OAuth 2.0 with PKCE (Proof Key for Code Exchange) for secure app authentication. This ensures merchants can safely grant your app access to their store data.
Every app interaction with merchant data goes through OAuth. The flow is:
redirect_uri with an authorization code Authorization: Bearer header Before redirecting the merchant, generate a code verifier and challenge:
import crypto from 'crypto';
// Generate a random code verifier (43-128 chars)
const codeVerifier = crypto.randomBytes(32).toString('base64url');
// Create S256 challenge
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
// Store codeVerifier in your session — you'll need it in step 3GET /oauth/authorize
?client_id=sf_app_abc123
&redirect_uri=https://myapp.com/auth/callback
&scope=read_products,read_orders,read_customers
&state=random_csrf_token_from_your_session
&response_type=code
&code_challenge={codeChallenge}
&code_challenge_method=S256| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Your app's client ID from the developer dashboard |
redirect_uri | Yes | Must exactly match the URI registered in your app settings |
scope | Yes | Comma-separated list of permissions (see Scopes section) |
state | Yes | Random string for CSRF protection — verify this on callback |
response_type | Yes | Must be code |
code_challenge | Yes | S256 hash of your code verifier |
code_challenge_method | Yes | Must be S256 |
After the merchant approves, Mercentia redirects to your redirect_uri:
GET https://myapp.com/auth/callback
?code=sf_auth_xxxxx
&state=random_csrf_token_from_your_session
&store_id=uuid_of_merchants_storestate parameter matches the one you stored in the session. If it doesn't match, reject the request — it may be a CSRF attack. Content-Type: application/json
{
"grant_type": "authorization_code",
"client_id": "sf_app_abc123",
"client_secret": "your_client_secret",
"code": "sf_auth_xxxxx",
"redirect_uri": "https://myapp.com/auth/callback",
"code_verifier": "original_code_verifier_from_step_1"
}{
"access_token": "sf_at_xxxxx",
"refresh_token": "sf_rt_xxxxx",
"token_type": "Bearer",
"expires_in": 86400,
"scope": "read_products,read_orders,read_customers",
"store_id": "merchant_store_uuid"
}Request only the scopes your app actually needs (least-privilege principle):
| Scope | Access | Description |
|---|---|---|
read_products | Read | View products, variants, images, collections |
write_products | Write | Create, update, delete products |
read_orders | Read | View orders, line items, fulfillments |
write_orders | Write | Update order status, add notes, create draft orders |
read_customers | Read | View customer profiles, addresses, tags |
write_customers | Write | Create, update customer records |
read_analytics | Read | View store analytics and reports |
read_inventory | Read | View stock levels and locations |
write_inventory | Write | Update stock levels |
read_shipping | Read | View shipping zones, rates, carriers |
write_shipping | Write | Create labels, update tracking |
read_discounts | Read | View discount codes and promotions |
write_discounts | Write | Create, update discount codes |
read_content | Read | View pages, blog posts, navigation |
write_content | Write | Create, update pages and content |
manage_checkouts | Write | Modify checkout flow and fields |
manage_fulfillments | Write | Create and update fulfillments |
read_store_settings | Read | View store configuration, currencies, languages |
Authorization: Bearer {token} headerX-Store-Id: {store_id} header with every request{
"grant_type": "refresh_token",
"client_id": "sf_app_abc123",
"client_secret": "your_client_secret",
"refresh_token": "sf_rt_xxxxx"
} When a merchant uninstalls your app, all tokens are automatically revoked. Your app will receive an app.uninstalled webhook. You must:
Verify a token and check current session info:
Authorization: Bearer sf_at_xxxxx
Response:
{
"store_id": "merchant_store_uuid",
"app_id": "your_app_uuid",
"scopes": ["read_products", "read_orders"],
"expires_at": "2026-04-22T14:30:00Z"
}| Requirement | Details |
|---|---|
| PKCE | Always use S256 code challenge — required for all apps |
| State parameter | Generate a cryptographically random state and verify on callback |
| HTTPS only | Redirect URIs and webhook URLs must use HTTPS |
| Token storage | Encrypt tokens at rest — use AES-256 or your platform's secrets manager |
| Client secret | Never expose in frontend code, logs, or version control |
| Scope minimisation | Only request permissions your app actually needs |
| Token refresh | Implement automatic refresh before expiry — don't let tokens expire mid-operation |
| Uninstall cleanup | Delete all merchant data and tokens when receiving app.uninstalled |
| Error | Code | Action |
|---|---|---|
invalid_client | 401 | Check your client_id and client_secret |
invalid_grant | 400 | Authorization code expired or already used |
invalid_scope | 400 | Requested scope not registered for your app |
access_denied | 403 | Merchant denied the permission request |
token_expired | 401 | Refresh the access token |
token_revoked | 401 | App was uninstalled — stop using this token |
insufficient_scope | 403 | Token doesn't have the required permission for this endpoint |
rate_limited | 429 | Back off and retry after the Retry-After header value |