Getting startedQuickstart

Quickstart

Ten minutes from registration to a verified, signed API call. Complete this before writing integration code — it isolates credential and signing problems from everything else.

  1. Register and create an app

    Sign up at open.mallplus.ph/partner/register, verify your email, complete your developer profile, then create an app. You will receive a sandbox client_id and client_secret.

    The secret is shown once. Store it in your secret manager immediately. If you lose it, rotate via POST /open/v1/credentials/rotate-secret — the old secret dies instantly.

  2. Confirm which host your credential belongs to

    Sandbox and production are separate hosts, each with its own credential kind. On production, a sandbox mp_… credential (and vice-versa) returns 401 INVALID_CREDENTIALS — a common first-call slip. Always pair the credential with its own host.

    CredentialBase URL
    mp_…https://sandbox.open.mallplus.ph
    mp_live_…https://open.mallplus.ph
  3. Sign your first request

    GET /open/v1/usage returns your own app's call statistics. It needs no seller authorization, so it verifies your signing in isolation.

    // Node 18+ — no dependencies
    import crypto from 'node:crypto';
    
    const clientId     = 'mp_REPLACE_WITH_YOUR_CLIENT_ID';
    const clientSecret = 'REPLACE_WITH_YOUR_CLIENT_SECRET';
    const baseUrl      = 'https://sandbox.open.mallplus.ph';
    
    const method    = 'GET';
    const path      = '/open/v1/usage';
    const query     = '';   // canonical query string — see Authentication
    const body      = '';   // empty for GET
    
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const nonce     = crypto.randomBytes(16).toString('hex');
    const bodyHash  = crypto.createHash('sha256').update(body).digest('hex');
    
    const baseString = [timestamp, clientId, method, path, query, bodyHash, nonce].join(':');
    const signature  = crypto.createHmac('sha256', clientSecret).update(baseString).digest('hex');
    
    const res = await fetch(baseUrl + path, {
      headers: {
        'X-MallPlus-Partner-Id':        clientId,
        'X-MallPlus-Timestamp':         timestamp,
        'X-MallPlus-Signature-Version': '3',
        'X-MallPlus-Nonce':             nonce,
        'X-MallPlus-Signature':         signature
      }
    });
    console.log(res.status, await res.json());
  4. Verify the result

    A correct call returns HTTP 200 with this shape (a brand-new app reports zeros):

    {
      "success": true,
      "data": {
        "summary":   { "totalCalls": 0, "successCalls": 0, "failCalls": 0, "successRate": 0 },
        "endpoints": [],
        "quota": {
          "window":    "per_minute",
          "limit":     600,
          "used":      1,
          "remaining": 599,
          "resetAt":   "2026-08-15T09:31:00.000Z"
        }
      }
    }

    If you did not get a 200, match the code below. Do not proceed until this call succeeds.

    CodeCauseFix
    401 INVALID_CREDENTIALSCredential kind does not match the hostUse the sandbox host with mp_…, production with mp_live_…
    401 INVALID_SIGNATUREBase string or secret mismatch, or uppercase hexPrint your base string and compare field-by-field with Authentication
    401 TIMESTAMP_EXPIREDClock drift > 90s, or milliseconds sentSend seconds; sync via NTP
    400 MISSING_NONCEX-MallPlus-Nonce omittedRequired whenever signature version is 3
    400 INVALID_NONCENonce is not 32–64 lowercase hexrandomBytes(16).toString('hex')
    403 FORBIDDENApp not active, or missing scopeCheck app status and granted scopes in the console
  5. Connect a seller

    Everything beyond /usage, /logs and /credentials acts on a seller's data and needs their authorization. Follow Seller authorization to obtain an access token, then send it as X-MallPlus-Access-Token alongside X-MallPlus-Seller-Id.

  6. Subscribe to webhooks

    Order intake is webhook-driven. Create a subscription for order.created before you build any polling logic — see Webhooks.