Getting Started > Authentication

Authentication

Last Updated: 2026-04-16

Overview

MallPlus Open Platform uses HMAC-SHA256 signature-based authentication for all API requests. Unlike token-based auth (OAuth, JWT), every request must be independently signed using your app's client secret. This ensures that requests cannot be replayed or tampered with in transit.

Important: Never expose your client secret in frontend code, browser-accessible files, or public repositories. All API calls must be made from your backend server.

Required Headers

Every API request must include the following three headers:

HeaderTypeDescription
X-MallPlus-Partner-IdString Your app's client ID (found in the App Details page in the console)
X-MallPlus-TimestampIntegerCurrent Unix timestamp in seconds (e.g., 1713254400)
X-MallPlus-SignatureStringHMAC-SHA256 hex digest of the signature base string

Signature Generation

Follow these steps to generate a valid request signature:

1

Get the current Unix timestamp (seconds)

Use your language's standard time library. The timestamp must be in seconds, not milliseconds.

2

Construct the signature base string

Five fields joined with colons:

{timestamp}:{clientId}:{METHOD}:{requestPath}:{sha256(body)}

Example (GET, empty body): 1713254400:app_ck9x2mf3w0001:GET:/open/v1/products:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

  • METHOD is the uppercase HTTP verb (GET, POST, …).
  • requestPath is the path only (e.g. /open/v1/products) — leading slash, no query parameters, and no URL-encoding. Send the path exactly as it appears in the endpoint reference.
  • sha256(body) is the hex SHA-256 of the raw request body; for empty bodies use SHA-256 of "".
  • For seller-scoped endpoints, send the seller token as the X-MallPlus-Access-Token / X-MallPlus-Seller-Id headers — they are not part of the base string.
3

Compute the HMAC-SHA256 hash

Use your app's client secret as the HMAC key and the base string as the message.

4

Hex-encode the result

Convert the binary HMAC output to a lowercase hexadecimal string and send it as the X-MallPlus-Signature header.

Public vs Shop Mode

The Open Platform has two signing contexts, and both use the identical base string{timestamp}:{clientId}:{METHOD}:{requestPath}:{sha256(body)}:

  • Public mode — calls that act on your app's own resources. Only the three signing headers (X-MallPlus-Partner-Id, X-MallPlus-Timestamp, X-MallPlus-Signature) are sent.
  • Shop mode — seller-scoped calls made on behalf of a connected seller. You additionally send the seller's access_token and shop_id as the X-MallPlus-Access-Token / X-MallPlus-Seller-Id headers.
Important: The access_token and shop_id are transmitted as HTTP headers and are never part of the base string. A Shop-mode signature is computed exactly like a Public-mode signature for the same method, path, and body.

Signature Format

The X-MallPlus-Signature header must be the HMAC-SHA256 digest encoded as a 64-character lowercase hexadecimal string. Other encodings are rejected with INVALID_SIGNATURE.

FormatExampleAccepted?
Lowercase hex (64 chars)e8864c9a…5b855Accepted
Uppercase hexE8864C9A…5B855Rejected
Base646IZMmq…W4VQ==Rejected

Most standard crypto libraries emit lowercase hex by default (Node's .digest('hex'), Python's .hexdigest(), PHP's hash_hmac()). If you build the hex string yourself, do not upper-case it.

Code Examples

Node.js

import crypto from 'crypto';

const clientId = 'app_ck9x2mf3w0001';
const clientSecret = 'sk_live_abc123...';
const method = 'GET';
const requestPath = '/open/v1/products';
const body = ''; // raw request body string; '' for GET

const timestamp = Math.floor(Date.now() / 1000);
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const baseString = `${timestamp}:${clientId}:${method}:${requestPath}:${bodyHash}`;
const signature = crypto
  .createHmac('sha256', clientSecret)
  .update(baseString)
  .digest('hex');

const response = await fetch('https://sandbox.open.mallplus.ph/open/v1/products', {
  headers: {
    'X-MallPlus-Partner-Id': clientId,
    'X-MallPlus-Timestamp': String(timestamp),
    'X-MallPlus-Signature': signature,
  },
});

Python

import hmac
import hashlib
import time
import requests

client_id = "app_ck9x2mf3w0001"
client_secret = "sk_live_abc123..."
method = "GET"
request_path = "/open/v1/products"
body = ""  # raw request body string; "" for GET

timestamp = str(int(time.time()))
body_hash = hashlib.sha256(body.encode()).hexdigest()
base_string = f"{timestamp}:{client_id}:{method}:{request_path}:{body_hash}"
signature = hmac.new(
    client_secret.encode(),
    base_string.encode(),
    hashlib.sha256
).hexdigest()

response = requests.get("https://sandbox.open.mallplus.ph/open/v1/products", headers={
    "X-MallPlus-Partner-Id": client_id,
    "X-MallPlus-Timestamp": timestamp,
    "X-MallPlus-Signature": signature,
})

PHP

<?php
$clientId = 'app_ck9x2mf3w0001';
$clientSecret = 'sk_live_abc123...';
$method = 'GET';
$requestPath = '/open/v1/products';
$body = ''; // raw request body string; '' for GET

$timestamp = time();
$bodyHash = hash('sha256', $body);
$baseString = "{$timestamp}:{$clientId}:{$method}:{$requestPath}:{$bodyHash}";
$signature = hash_hmac('sha256', $baseString, $clientSecret);

$ch = curl_init('https://sandbox.open.mallplus.ph/open/v1/products');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "X-MallPlus-Partner-Id: {$clientId}",
    "X-MallPlus-Timestamp: {$timestamp}",
    "X-MallPlus-Signature: {$signature}",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.net.http.*;
import java.net.URI;

String clientId = "app_ck9x2mf3w0001";
String clientSecret = "sk_live_abc123...";
String method = "GET";
String requestPath = "/open/v1/products";
String body = ""; // raw request body string; "" for GET

long timestamp = System.currentTimeMillis() / 1000;
MessageDigest sha = MessageDigest.getInstance("SHA-256");
String bodyHash = bytesToHex(sha.digest(body.getBytes()));
String baseString = timestamp + ":" + clientId + ":" + method + ":" + requestPath + ":" + bodyHash;

Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(clientSecret.getBytes(), "HmacSHA256"));
byte[] hash = mac.doFinal(baseString.getBytes());
String signature = bytesToHex(hash);

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://sandbox.open.mallplus.ph/open/v1/products"))
    .header("X-MallPlus-Partner-Id", clientId)
    .header("X-MallPlus-Timestamp", String.valueOf(timestamp))
    .header("X-MallPlus-Signature", signature)
    .build();

Timestamp Validation

The X-MallPlus-Timestamp header must be a Unix epoch integer in seconds (e.g. 1716451200), not milliseconds. The MallPlus API server validates that it is within 90 seconds of the server's current time.

Requests with timestamps outside this window are rejected with a TIMESTAMP_EXPIRED error. This prevents replay attacks where a captured request is resent at a later time.

Tip: Ensure your server's clock is synchronized via NTP. Clock drift beyond 90 seconds will cause all requests to fail. Most cloud providers keep system clocks synchronized automatically.

Seller-Scoped API Calls

For seller-scoped API calls where a partner app acts on behalf of a specific seller, MallPlus uses an OAuth-style authorization flow. The seller grants your app access through a consent page, and your app receives scoped access tokens tied to that seller.

For the full authorization flow, token management, and code examples, see Seller Authorization.

Common Errors

Error CodeHTTP StatusDescriptionSolution
INVALID_SIGNATURE401The computed signature does not match Verify your base string format and client secret. Check for extra whitespace or encoding issues.
TIMESTAMP_EXPIRED401Timestamp is more than 90 seconds from server time Sync your server clock via NTP. Ensure you're sending seconds, not milliseconds.
INACTIVE_APP403The app associated with the client ID is not active Check the app status in the console. Apps must be approved and active to make API calls.
MISSING_HEADER400One or more required authentication headers are missingInclude all three required headers in every request.
SCOPE_DENIED403The app does not have the required scope for this endpoint Request the necessary scope in the App Management page, then wait for approval.