Help > FAQs

Frequently Asked Questions

Last Updated: 2026-05-05

Common questions partner developers ask while integrating with MallPlus Open Platform. If your question isn't here, see the topical guides under Getting Started or open a support ticket.

Authentication & credentials

Where do I get my client_id and client_secret?

Sign in to the partner console, create an app on /apps/create, and copy the credentials shown immediately after creation. The client_secret is shown ONCE — store it in a secret manager. If you lose it, rotate via /apps/<id> → Rotate Secret.

My request returns 401 Invalid signature. What do I check?

In order: (1) Is the timestamp in seconds (not milliseconds)? (2) Is the timestamp within ±60s of server time? (3) Is the base string exactly `${timestamp}:${clientId}:${METHOD}:${path}:${sha256(body)}` for HMAC-v2? (4) Is the signature lowercase hex? (5) Are you signing with the secret from the same environment as the clientId (sandbox vs live)?

How do I rotate a leaked secret?

POST /open/v1/credentials/rotate-secret (sandbox) or /rotate-live-secret (production, app status = live). The response includes the new secret ONCE. The old secret is invalidated immediately — schedule the rollout to coincide with cache flush in your app.

How long does an access token live?

Access token: 4 hours. Refresh token: 30 days. Use POST /open/v1/auth/token/refresh to mint a new pair. The refresh token is rotated each call.

Can I use HMAC v1 (the old base string without method/body)?

Yes — v1 is accepted with a deprecation warning. Plan to migrate to v2 by your next minor release; v1 will be removed in a future version.

Webhooks

How do I verify a webhook came from MallPlus?

Check the X-MallPlus-Signature header: it is HMAC-SHA256(timestamp:event:body, secret). Use the SAME client_secret you sign requests with (live secret for live apps, sandbox secret otherwise). Reject requests where the timestamp is more than 5 minutes old.

What is the retry policy?

A delivery is attempted inline once, then queued for retry on any non-2xx or network error. Backoff is exponential (4s × 4^attempt) up to 3 attempts. After the last attempt the row goes to status="dead_letter" and stays for forensic inspection — it does not auto-retry.

What happens if my callback URL is briefly down?

It is retried per the schedule above (~1 hour total wall time across 3 attempts). If it stays down past that, the event is dead-lettered. We do NOT replay dead-lettered events automatically — open a support ticket if you need a manual replay.

My subscription was created but I receive no events. Why?

Common causes: (1) the subscription is inactive (set isActive=true), (2) the partner app has been soft-deleted, (3) the callback URL fails the SSRF guard (private IPs blocked), (4) every delivery is being dead-lettered — check /tools/push-log.

Can the same (event, callback) be subscribed twice?

No. The `(partner_app_id, event_type, callback_url)` triplet has a unique partial index. The second create returns 409 CONFLICT.

Rate limits

What are the limits?

Default: 100 requests/minute on read endpoints, 30 requests/minute on write endpoints, scoped per app. The limiter applies per-IP first (cheap pre-auth shield) then per-app (verified after partner-auth). Headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset are returned on every response.

I got 429 — what now?

Honour the Retry-After header (or back off using X-RateLimit-Reset). Implement exponential backoff with jitter on retries; do not hammer the endpoint or your IP bucket can be temporarily blocked too.

Can I get higher limits?

Yes for production-grade integrations. Open a support ticket with: app name, expected peak QPS, and the endpoints you need lifted.

Sandbox vs production

How do I get production credentials?

Submit a Go Live application from /apps/<id>/go-live. An admin reviews the production URL, integration description, and any KYC documents. On approval, you receive `clientIdLive` and `clientSecretLive` (shown once). Status moves from `pending_go_live` → `live`.

Can I use the same app for both?

Yes. One app holds both sandbox (`mp_*`) and live (`mp_live_*`) credentials. Use whichever is appropriate — the API auto-detects which environment from the credential prefix.

When is sandbox data reset?

Sandbox data is NOT reset on a schedule — it accumulates as you test. If you want a clean slate, use the seed endpoints in dev/staging, or request a manual reset via support ticket.

Which secret signs webhooks in production?

The live secret if your app status is `live`. Sandbox apps sign with the sandbox secret. The dispatcher always uses the secret matching the app environment — your verifier should pick whichever was issued to you most recently for the running env.

Errors & debugging

What is the response shape on error?

Always `{ success: false, error: { code, message } }`. The `code` field is a stable enum from `ERROR_CODES` (e.g. UNAUTHORIZED, VALIDATION_ERROR, NOT_FOUND, RATE_LIMITED). Do not parse `message` — it is human-readable and may change.

I see TIMESTAMP_EXPIRED but my clock looks correct.

The window is ±60s. If your container drifts, sync NTP. If you are running in a serverless cold-start, use the request reception time, not your function start time. The server compares against UTC seconds — make sure you are not using milliseconds.

My authenticated request gets a 404 instead of the expected 403.

That is by design — cross-tenant access (e.g. another seller's order) returns 404, not 403, to avoid leaking the existence of resources you don't own.

How do I get the request ID for support?

Every response includes an `X-Request-Id` header. Include it in your support ticket so we can grep the access log directly.

Support

Where do I open a ticket?

Partner console → /support/raise-ticket. Categories: API issue, Authentication, Webhooks, Sandbox & Testing, Account & App Management, Other. Attach the X-Request-Id from any failing call so we can pull the access log.

How fast do you respond?

High priority: same business day. Medium: 1–2 business days. Low: best-effort. We bump priority automatically on production-blocking incidents reported with a request id.

Where is the changelog?

/docs/changelog. Subscribe via the partner-portal email preferences in /settings/account. Breaking changes are flagged at least 30 days in advance.

Still stuck?

Open a support ticket and include the X-Request-Id header from any failing call. We respond within 1–2 business days.