# Authentication

Scope: This guide applies to partner-facing endpoints under /partner/** in omni-partner-api. Internal or service-to-service endpoints may use different mechanisms.

# Overview

Omni Partner API uses HMAC-SHA256 request signing. Partners authenticate each request using their Client ID and Secret Key. When acting on behalf of a specific store, partners also pass a Store Client ID and Store Token for authorization.

  • Partner identity: clientId + secretKey
  • Optional store delegation: storeClientId + storeToken
  • Guarded surface: /partner/** (validated by our HMAC guard)

# Credentials & Delegation

# Partner credentials

  • Client ID (public): e.g. ptnr_xxxxx
  • Secret Key (private): shared out-of-band; do not embed in client-side apps.

# Store delegation

Include these when calling endpoints that act on a store’s resources:

  • Store Client ID (public): e.g. str_xxxxx
  • Store Token (private, revokable/expirable): issued during onboarding/rotation.

# Required headers

Header Description Example
x-partner-client-id Your partner clientId (public identifier). ptnr_AbC123
x-store-client-id Required when acting on a store; that store’s public ID. str_9xyZ
x-store-token Required when x-store-client-id is present; bearer-like store token (included in signature when sent). stkn_1G_R3r_5QTvwr_0O
x-timestamp UNIX epoch milliseconds (integer). 1709024577000
x-signature HMAC signature formatted as sha256=<hex> (see HMAC signature). sha256=<hex>

Notes
• Header names are case-insensitive in HTTP; when signing, canonicalize to lowercase exactly as listed above. Values remain case-sensitive.
x-store-client-id and x-store-token are required only for store-scoped calls.


# HMAC signature

Generate x-signature: sha256=<hex> using your partner secretKey over a canonical base string.

# Canonical components

Component How to build (public-safe)
METHOD GET, POST, DELETE, PATCH, PUT (Uppercase HTTP method).
PATH Request path with /api/v1 prefix stripped and query string removed. Path params remain. Example: /partner/stores/catalog/02b65657-bfcd-47ba-9f91-ec67e7b5913e.
Headers (lines) Include only these headers if present, lowercased and sorted lexicographically; each line is name:value (no spaces):
x-partner-client-id
x-store-client-id
x-store-token
x-timestamp (epoch milliseconds)
BODYHASH Lowercase hex SHA-256 of the stringified request body (JSON.stringify style). For no body, use SHA-256 of the empty string: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

# Base string layout

Join the components with a single newline \n:

METHOD
PATH
HEADER1:VALUE
HEADER2:VALUE
...
BODYHASH

# Signature calculation

signature_hex = HEX( HMAC-SHA256( secretKey, baseString ) )
x-signature: sha256=<signature_hex>

# Reference

These mirror your flow. Replace <epoch_ms> with the integer epoch (milliseconds) you send in x-timestamp. For no-body requests, the last line is the SHA-256 of the empty string.

# API GET

Inputs

  • Method: GET
  • Path: /partner/stores/catalog/02b65657-bfcd-47ba-9f91-ec67e7b5913e
  • Headers present:
    x-partner-client-id: ptnr_1s4UqMnO64,
    x-store-client-id: str_TGIxyboe7-Rz,
    x-store-token: stkn_1G_R3r_5QTvwr_0O,
    x-timestamp: 1709024577000

Base string (headers sorted alphabetically)

GET
/partner/stores/catalog/02b65657-bfcd-47ba-9f91-ec67e7b5913e
x-partner-client-id:ptnr_1s4UqMnO64
x-store-client-id:str_TGIxyboe7-Rz
x-store-token:stkn_1G_R3r_5QTvwr_0O
x-timestamp:1709024577000
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

# API POST

Inputs

  • Method: POST
  • Path: /partner/stores/catalog/sync
  • Headers present:
    x-partner-client-id: ptnr_1s4UqMnO64,
    x-store-client-id: store_NB5DgDcEoWEu,
    x-store-token: stkn_Xfe-j_OKH5H2Xg66,
    x-timestamp: 1709024577000
  • Body: (use the exact JSON bytes you transmit)

Body hash

<BODY_SHA256_HEX>

Base string

POST
/partner/stores/catalog/sync
x-partner-client-id:ptnr_1s4UqMnO64
x-store-client-id:store_NB5DgDcEoWEu
x-store-token:stkn_Xfe-j_OKH5H2Xg66
x-timestamp:1709024577000
<BODY_SHA256_HEX>

Important: Body hash must be computed over the exact string you send on the wire. Whitespace or field-order changes will alter the hash.


# Example Request (cURL)

curl -X \
 POST "https://api.example.com/partner/products?lang=id&sku=SKU-1"   -H \
 "Content-Type: application/json"   -H \
 "x-partner-client-id: ptnr_AbC123"   -H \
 "x-store-client-id: str_9xyZ"   -H \
 "x-store-token: stkn_example"   -H \
 "x-timestamp: 1709024577000"   -H \
 "x-signature: sha256=<computed-hex>"   -d '{"name":"Sample","sku":"SKU-1"}'

# Example Responses

{
    "success": false,
    "error": {
        "code": "AUTH_003",
        "message": "Expired or invalid timestamp",
        "details": {
            "timestamp": "2025-08-27T08:55:28.453Z",
            "hint": "Request timestamp must be within 300 seconds",
            "context": {
                "providedTimestamp": 1756284543435,
                "currentTime": 1756284928453,
                "ageSeconds": 385
            }
        }
    },
    "requestId": "51b97684-b509-4940-b315-12d49b2c0f28"
}
{
  "success": false,
  "error": {
    "code": "VAL_001",
    "message": "storeId is required"
  },
  "requestId": "9b7a4d44-1d7e-4d27-8d6b-1e9a7c2b4f01"
}

Notes
• Use the returned requestId when contacting support.
• Ensure your client clock is NTP-synced. If you retry, recompute both x-timestamp and x-signature.


# Verification notes

  • x-timestamp must be within ±5 minutes of server time (epoch milliseconds).
  • The path you sign must match the path you send (after stripping /api/v1, without query).
  • Headers included in the signature must also be sent with the same values.
  • BODYHASH must reflect the exact stringified body sent.

# Common pitfalls

  • Signing the path with query parameters attached.
  • Not lowercasing header names before sorting into the base string.
  • Hashing a pretty-printed body but sending minified (or vice versa).
  • Forgetting to include x-store-client-id / x-store-token in the canonical headers when calling store-scoped endpoints.