Skip to content
ZytePedevelopersDashboard
DocsGet started
Get started

Authentication

Sign exact request bytes and distinguish the two API authentication families.

Merchant HMAC

Send X-API-KEY-ID, X-API-TIMESTAMP (Unix seconds), a unique X-API-NONCE and X-API-SIGNATURE. For JSON POST requests also send Content-Type: application/json. The signature is lowercase hexadecimal HMAC-SHA256 over six LF-separated lines:

TEXT
METHOD
PATH
QUERY_STRING
TIMESTAMP
NONCE
SHA256_HEX(RAW_BODY)
 

METHOD is uppercase. PATH includes resolved identifiers. QUERY_STRING excludes the question mark and preserves outgoing order and encoding. Hash the exact UTF-8 body; GET hashes the empty string, not {}.

import { createHash, createHmac, randomUUID } from 'node:crypto';
 
export function signedHeaders({ method, path, query = '', body = '', keyId, secret,
  timestamp = String(Math.floor(Date.now() / 1000)), nonce = randomUUID().replaceAll('-', '') }) {
  if (!keyId || !secret) throw new Error('Server-side API credentials are required');
  if (path.includes('{') || path.includes('}')) throw new Error('Resolve path variables before signing');
  const bodyHash = createHash('sha256').update(body, 'utf8').digest('hex');
  const canonical = [method.toUpperCase(), path, query, timestamp, nonce, bodyHash].join('\n');
  return {
    'X-API-KEY-ID': keyId,
    'X-API-TIMESTAMP': timestamp,
    'X-API-NONCE': nonce,
    'X-API-SIGNATURE': createHmac('sha256', secret).update(canonical, 'utf8').digest('hex'),
  };
}
 

Serialize a POST body once and pass that same string to signing and fetch. Regenerate timestamp/nonce/signature for each HTTP request. For a payout retry, retain the same business idempotency key.

Plugin signatures

Plugin operations use X-ZytePe-Api-Key, X-ZytePe-Timestamp and X-ZytePe-Signature. Their canonical string is separate:

TEXT
timestamp.METHOD.PATH.raw_body
 

The reviewed local handler uses Unix seconds and a lowercase hexadecimal HMAC-SHA256 digest. GET uses an empty raw body. This clarifies omissions in the supplied contract; confirm the deployed handler matches before rollout. Do not reuse the six-line HMAC canonical string with plugin headers.

Webhook verification is separate

The webhook contract signs a JSON payload, not an API request canonical string. Never reuse either request-signing helper for incoming callbacks.

Troubleshoot signatures

Check the full secret, synchronized timestamp, fresh nonce, resolved path and exact body bytes. Pretty-printing JSON after signing changes the signature. Do not log secrets or raw personal data while diagnosing a failed request.