Signed API Authentication
This page describes how to authenticate server-side requests to the Stylux API with HMAC signed API keys (SIGNED_HMAC). Use it for GraphQL (POST /graphql) and REST calls that require a signed key.
Credentials
You receive an API key and an API secret. Treat the secret as a credential; store it securely (for example using a secrets manager). Keep it out of git (gitignore any env or secret files); never commit it to source control, never expose it in client-side code or logs, and never paste it into an LLM or other third party tool. If the secret is compromised or you suspect it has been, notify Stylux immediately so the key can be rotated.
Frontend / storefront keys that use unsigned web auth are separate; they must not use or expose the signed secret. See Onboarding for how keys are issued.
Headers
Each signed request needs these headers:
| Header | Required | Description |
|---|---|---|
X-STYLUX-API-KEY | Yes | Public API key |
X-STYLUX-TIMESTAMP | Yes | Current time in Unix milliseconds (for example 1735689600000) |
X-STYLUX-NONCE | Yes | Unique value for this request; do not reuse |
X-STYLUX-SIGNATURE | Yes | Base64 HMAC of the canonical string (see below) |
X-STYLUX-SIGNATURE-ALGORITHM | No | HMAC algorithm used for X-STYLUX-SIGNATURE: sha256 or sha512 |
Rules:
- Timestamp must be within 5 minutes of server time.
- Generate a new nonce for every request. A previously used nonce will fail (replay protection).
- HTTP method in the signature must be uppercase (
POSTfor GraphQL;GET,POST, etc. for REST). - Sign the exact body you send. For JSON bodies, use compact JSON (no extra spaces or newlines). The server verifies against the parsed then re-serialized body, so pretty printed JSON will fail. If there is no body, use an empty string.
- The URL in the signature is pathname plus query string only (host is ignored). Query parameters are sorted alphabetically. For GraphQL this must be
/graphql(including the leading slash).
Signature algorithm
Supported algorithms are sha512 and sha256 (case insensitive). When X-STYLUX-SIGNATURE-ALGORITHM is omitted or empty, the server verifies with sha512.
If you send the header, its value must match the algorithm you used to create X-STYLUX-SIGNATURE. An unsupported value fails auth. Prefer sending the header explicitly when you use sha256; for sha512 you may omit it or send sha512.
How to generate the signature
Concatenate these pieces in order, with no separators, then HMAC with your chosen algorithm (sha512 by default) using your API secret. Encode the digest as Base64.
- Timestamp, as the same string you put in
X-STYLUX-TIMESTAMP - HTTP method, uppercase
- Sanitized URL (
pathname+ sorted query) - Request body (compact JSON string, or
''when empty) - Nonce, as the same string you put in
X-STYLUX-NONCE
Pseudocode:
canonical = timestamp + method + sanitizedUrl + body + nonce
signature = Base64( HMAC(algorithm, key = apiSecret, data = canonical) )
algorithm is sha512 or sha256. Use the same value in X-STYLUX-SIGNATURE-ALGORITHM when you send that header.
To sanitize the URL: parse it, sort query parameters alphabetically, then use pathname + search. For GraphQL there is no query string; use /graphql (including the leading slash). A full URL such as https://api.stylux.io/graphql sanitizes to the same /graphql. Do not use graphql without the slash.
Working example (GraphQL)
These values are for verifying your signer; they are not valid live credentials.
- API secret:
example-secret - Timestamp:
1735689600000 - Method:
POST - URL:
/graphql - Nonce:
nce_01EXAMPLE - Body:
{
"query": "{ partnerFulfillments(first: 50) { nodes { id } } }"
}When signing, serialize that body as compact JSON (no spaces or newlines). The compact form of the body above is {"query":"{ partnerFulfillments(first: 50) { nodes { id } } }"}.
Canonical string; concatenate in this order, with no separators or extra whitespace:
1735689600000POST/graphql{"query":"{ partnerFulfillments(first: 50) { nodes { id } } }"}nce_01EXAMPLE
Expected X-STYLUX-SIGNATURE with sha512 (default; omit the algorithm header, or send X-STYLUX-SIGNATURE-ALGORITHM: sha512):
NwwPPaomjo5kH0Vyj8di2fYQlfX44M/DEly8VuZQKrvq8MExO7pm7RY9trD6E97otaVpDO+/tgQNPnbnUDnu2w==
Expected X-STYLUX-SIGNATURE for the same canonical string with sha256 (send X-STYLUX-SIGNATURE-ALGORITHM: sha256):
8/jUrF3OvAuMakvcP2ghsAjTZ1WC0erfXB9NiUSRckM=
Request shape (GraphQL)
POST /graphql HTTP/1.1
Host: api.stylux.io
Accept: application/json
Content-Type: application/json
X-STYLUX-API-KEY: <your-api-key>
X-STYLUX-TIMESTAMP: 1735689600000
X-STYLUX-NONCE: nce_01EXAMPLE
X-STYLUX-SIGNATURE: <base64-hmac>
X-STYLUX-SIGNATURE-ALGORITHM: sha512
{
"query": "...",
"variables": {
"first": 50,
"after": null,
"filter": null
}
}X-STYLUX-SIGNATURE-ALGORITHM is optional; omit it to default to sha512.
Invalid or missing auth returns GraphQL UNAUTHORIZED (HTTP 401). Sign every request separately (new nonce, new timestamp, new signature over the new body).
Environments
| Environment | GraphQL URL | REST base |
|---|---|---|
| Production | https://api.stylux.io/graphql | https://api.stylux.io |
| Staging | https://api-stg.stylux.io/graphql | https://api-stg.stylux.io |
| Development | https://api-dev.stylux.io/graphql | https://api-dev.stylux.io |
Use Content-Type: application/json and Accept: application/json for GraphQL and JSON REST bodies.
Related
- Inventories API (GraphQL)
- Decoration Partner Orders API (GraphQL)
- Order Update Webhooks (outbound API signing is the same; inbound webhook delivery uses a different HMAC)
Updated 8 days ago
