Getting Started > Seller Authorization

Seller Authorization

Last Updated: 2026-07-03

Overview

MallPlus uses an OAuth-style seller authorization flow when a partner app needs to act on behalf of a specific seller. This is the flow you use for seller-scoped routes such as orders, inventory, fulfillments, and other operational APIs.

The partner generates a signed authorization URL, the seller grants consent, MallPlus returns a one-time authorization code, and the partner exchanges that code for an access token and refresh token tied to that seller-app pair.

What this page covers:

  • How to generate and exchange seller authorization credentials
  • How access tokens and refresh tokens rotate over time
  • How revocation works from both the partner and seller side
  • When the seller must re-authorize your app

Authorization Flow

The seller authorization flow consists of 6 steps:

Partner App MallPlus API Seller | | | |-- 1. Auth URL -->| | | |-- 2. Consent ->| | |<- Seller grants| |<- 3. code -------| | |-- 4. token ------>| | |<- access + refresh| | |-- 5. API calls -->| | |-- 6. refresh ---->| |

Step 1: Generate Signed Authorization URL

Your backend generates an HMAC-signed URL that the seller will visit. The signature proves the link came from your app and prevents tampering.

GET https://sandbox.open.mallplus.ph/open/v1/auth/authorize
  ?client_id=mp_your_client_id
  &redirect_uri=https://yourapp.com/callback
  &timestamp=1713254400
  &sign=a1b2c3d4e5f6...

The sign parameter is an HMAC-SHA256 signature. See HMAC for Auth URL below.

Redirect URI must match: the domain of redirect_uri must exactly match the redirect domain registered for your app. Subdomains are rejected. A mismatch fails with REDIRECT_URL_MISMATCH and the seller is never redirected — set your app's redirect domain before starting the flow.

Step 2: Seller Visits URL and Consents

MallPlus validates the signature and shows a consent page with your app's name, category, and requested scopes. The seller signs in with their seller account and clicks Authorize to grant access.

If the seller declines: clicking Deny issues no authorization code and redirects back to your registered redirect URL with an error=access_denied query parameter — for example https://yourapp.com/callback?error=access_denied. Handle this as a normal user cancellation, not a failure.

Step 3: Receive Authorization Code

After consent, MallPlus redirects back to your redirect_uri with a one-time authorization code and the seller ID:

https://yourapp.com/callback?code=abc123xyz&seller_id=seller_nike_ph
Important: The authorization code expires in 10 minutes and can only be used once. Exchange it for tokens immediately.

Step 4: Exchange Code for Tokens

Your backend exchanges the authorization code for an access token and refresh token. This request requires standard HMAC authentication headers.

POST /open/v1/auth/token
Headers:
  X-MallPlus-Partner-Id: mp_your_client_id
  X-MallPlus-Timestamp: 1713254400
  X-MallPlus-Signature: <hmac_signature>
  Content-Type: application/json

Body:
{
  "code": "abc123xyz",
  "client_id": "mp_your_client_id",
  "seller_id": "seller_nike_ph"
}

Response:
{
  "success": true,
  "data": {
    "access_token": "at_xxxxxxxxxxxx",
    "refresh_token": "rt_xxxxxxxxxxxx",
    "expires_in": 14400,
    "seller_id": "seller_nike_ph",
    "seller_name": "Nike Philippines",
    "scopes": ["catalog:read", "orders:read"]
  }
}

Step 5: Make Seller-Scoped API Calls

For seller-scoped routes, send the normal HMAC headers plus the seller token headers. Calls without the seller headers return 401 SELLER_TOKEN_REQUIRED.

HeaderDescription
X-MallPlus-Partner-IdYour app's client ID
X-MallPlus-TimestampCurrent Unix timestamp in seconds
X-MallPlus-SignatureHMAC-SHA256 signature
X-MallPlus-Access-TokenSeller-scoped access token
X-MallPlus-Seller-IdSeller ID paired with the access token

Step 6: Refresh Tokens Before They Expire

Access tokens expire after 4 hours. Use the refresh token to request a new token pair. Refresh tokens are single-use: once a refresh succeeds, the old refresh token is invalid immediately.

POST /open/v1/auth/token/refresh
Headers:
  X-MallPlus-Partner-Id: mp_your_client_id
  X-MallPlus-Timestamp: 1713254400
  X-MallPlus-Signature: <hmac_signature>
  Content-Type: application/json

Body:
{
  "refresh_token": "rt_xxxxxxxxxxxx",
  "client_id": "mp_your_client_id",
  "seller_id": "seller_nike_ph"
}

Response:
{
  "success": true,
  "data": {
    "access_token": "at_new_token",
    "refresh_token": "rt_new_token",
    "expires_in": 14400
  }
}
Important: If the same refresh token is reused after rotation, MallPlus treats that as a credential-theft signal. The API returns REFRESH_TOKEN_REUSED and revokes the entire authorization chain for that seller-app pair.

HMAC Signature for Auth URL

The authorization URL uses this signature base string:

{timestamp}:{clientId}:/open/v1/auth/authorize

This is the same format as the standard API authentication signature, using the fixed path /open/v1/auth/authorize.

const baseString = `${timestamp}:${clientId}:/open/v1/auth/authorize`;
const sign = crypto.createHmac('sha256', clientSecret).update(baseString).digest('hex');

HMAC for Token Calls

The token exchange, refresh, and revoke endpoints all use the standard HMAC base string format:

{timestamp}:{clientId}:{requestPath}

For example, when calling POST /open/v1/auth/token/refresh:

const baseString = `${timestamp}:${clientId}:/open/v1/auth/token/refresh`;
const signature = crypto.createHmac('sha256', clientSecret).update(baseString).digest('hex');
Note: For regular seller-scoped API calls the access token and seller ID are sent as the X-MallPlus-Access-Token / X-MallPlus-Seller-Id headers and are not part of the signature base string — the base string is identical to a public-mode request (MP-7894).

Token Lifecycle

CredentialLifetime Rotation / End Notes
authorization_code10 minutesSingle-useExpires quickly and is consumed immediately on token exchange
access_token4 hoursReplaced on refreshUsed on seller-scoped API calls
refresh_token30 daysSingle-use rotationOld token is invalid immediately after a successful refresh
authorization grant365 daysSeller must re-authorize Refresh cannot extend past the overall 365-day authorization wall
Expiry warning: MallPlus sends an email notification and a console notification 7 days before the 365-day authorization expires so the developer can renew access before calls begin failing.

Manage Authorizations

Partners can review connected sellers from the app detail area in the partner console. Open your app and go to the Authorized Sellers page to inspect current authorizations.

The authorization list shows:

  • Shop Name and Shop ID for the connected seller
  • Scopes granted to your app for that seller
  • Authorized Date so you know when access was first granted
  • Expiry Date — the end of the 365-day authorization window
  • StatusActive, Expiring Soon (within 7 days of the authorization expiry), or Expired
Use this page to monitor which shops are currently connected, spot authorizations nearing expiry (Expiring Soon rows are highlighted), and quickly revoke access when needed. Revoked authorizations are removed from the list.
Expiry warning: When an authorization is within 7 days of expiry, a banner also appears on the App Detail page identifying the affected shop, and the developer receives an email. Refresh the token via the API before it expires, otherwise the seller must re-authorize.

Revocation

Authorizations can be revoked from either side. Once revoked, the seller-app pair stops working immediately and subsequent seller-scoped API calls fail until the seller completes the authorization flow again.

Partner-side revocation

Partners can revoke access from the console or programmatically with POST /open/v1/auth/revoke. This endpoint requires HMAC headers because only the owning partner can revoke its own authorizations.

POST /open/v1/auth/revoke
Headers:
  X-MallPlus-Partner-Id: mp_your_client_id
  X-MallPlus-Timestamp: 1713254400
  X-MallPlus-Signature: <hmac_signature>
  Content-Type: application/json

Body:
{
  "client_id": "mp_your_client_id",
  "seller_id": "seller_nike_ph"
}

Seller-side revocation

Sellers manage your app from Shop Settings → Partner Management in their MallPlus seller center. The page lists every app they have authorized across three tabs — Using (active), Expired (past the 365-day window), and Unlink (revoked) — and lets the shop owner unlink an app after a confirmation step.

When a seller unlinks your app, both the access token and the refresh token for that seller-app pair stop working immediately, and the seller must grant consent again before you can continue calling seller-scoped APIs.

Endpoints Reference

All token and authorization failures should use the standardized error envelope below so partner systems can handle refresh, expiry, and revocation failures consistently.

{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}
MethodEndpointAuthDescription
GET/open/v1/auth/authorizeQuery params (HMAC signed)Initiate seller authorization
POST/open/v1/auth/seller-verifyConsent formValidate seller login and create auth code
POST/open/v1/auth/denyConsent form Seller declines consent; returns the validated redirect target
POST/open/v1/auth/tokenHMAC headersExchange auth code for access + refresh tokens
POST/open/v1/auth/token/refreshHMAC headersRotate the refresh token for a fresh token pair
POST/open/v1/auth/revokeHMAC headersRevoke a seller authorization for your app

Full Flow Example (Node.js)

import crypto from 'crypto';

const CLIENT_ID = 'mp_your_client_id';
const CLIENT_SECRET = 'sk_live_your_secret';
const REDIRECT_URI = 'https://yourapp.com/callback';

function sign(path, timestamp) {
  const baseString = `${timestamp}:${CLIENT_ID}:${path}`;
  return crypto.createHmac('sha256', CLIENT_SECRET).update(baseString).digest('hex');
}

function generateAuthUrl() {
  const timestamp = Math.floor(Date.now() / 1000);
  const baseString = `${timestamp}:${CLIENT_ID}:/open/v1/auth/authorize`;
  const signValue = crypto.createHmac('sha256', CLIENT_SECRET).update(baseString).digest('hex');

  const url = new URL('https://sandbox.open.mallplus.ph/open/v1/auth/authorize');
  url.searchParams.set('client_id', CLIENT_ID);
  url.searchParams.set('redirect_uri', REDIRECT_URI);
  url.searchParams.set('timestamp', String(timestamp));
  url.searchParams.set('sign', signValue);
  return url.toString();
}

async function exchangeCode(code, sellerId) {
  const timestamp = String(Math.floor(Date.now() / 1000));
  const path = '/open/v1/auth/token';

  const res = await fetch(`https://sandbox.open.mallplus.ph${path}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-MallPlus-Partner-Id': CLIENT_ID,
      'X-MallPlus-Timestamp': timestamp,
      'X-MallPlus-Signature': sign(path, timestamp),
    },
    body: JSON.stringify({
      code,
      client_id: CLIENT_ID,
      seller_id: sellerId,
    }),
  });

  return res.json();
}

async function refreshTokens(refreshToken, sellerId) {
  const timestamp = String(Math.floor(Date.now() / 1000));
  const path = '/open/v1/auth/token/refresh';

  const res = await fetch(`https://sandbox.open.mallplus.ph${path}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-MallPlus-Partner-Id': CLIENT_ID,
      'X-MallPlus-Timestamp': timestamp,
      'X-MallPlus-Signature': sign(path, timestamp),
    },
    body: JSON.stringify({
      refresh_token: refreshToken,
      client_id: CLIENT_ID,
      seller_id: sellerId,
    }),
  });

  return res.json();
}

async function revokeAuthorization(sellerId) {
  const timestamp = String(Math.floor(Date.now() / 1000));
  const path = '/open/v1/auth/revoke';

  const res = await fetch(`https://sandbox.open.mallplus.ph${path}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-MallPlus-Partner-Id': CLIENT_ID,
      'X-MallPlus-Timestamp': timestamp,
      'X-MallPlus-Signature': sign(path, timestamp),
    },
    body: JSON.stringify({
      client_id: CLIENT_ID,
      seller_id: sellerId,
    }),
  });

  return res.json();
}

Error Handling

Error CodeHTTPCauseWhat to do
BAD_REQUEST400 Authorization code is malformed or does not match the authenticated partner request Restart the seller consent flow and exchange the new code immediately
AUTH_CODE_USED401The authorization code has already been exchanged once Codes are single-use — restart the seller consent flow to get a fresh code
AUTH_CODE_EXPIRED401The authorization code is past its 10-minute window Restart the seller consent flow and exchange the new code immediately
REDIRECT_URL_MISMATCH400 The redirect_uri domain does not match the redirect domain registered for the app Use a redirect URI on the exact registered domain; update the app's redirect domain if it changed
UNAUTHORIZED401 Refresh token is invalid for the authenticated app, expired, rotated out, or no longer usable If you no longer have a valid token pair, ask the seller to re-authorize
REFRESH_TOKEN_REUSED401A previously-rotated refresh token was presented again Stop retrying. Treat this as a hard revoke and drive the seller through consent again
RE_AUTHORIZATION_REQUIRED401The 365-day seller authorization validity has expired Ask the seller to grant consent again; refresh cannot extend past this wall
SELLER_TOKEN_REQUIRED401A seller-scoped route was called without seller token headers Send X-MallPlus-Access-Token and X-MallPlus-Seller-Id with the HMAC headers
TIMESTAMP_EXPIRED401The HMAC timestamp is outside the allowed windowSync your server clock and regenerate the request