Tracepos Developer HubTracepos Developer Hub
Home
Guide
POS & Integrations
API Reference
Webhooks
Home
Guide
POS & Integrations
API Reference
Webhooks
  • Getting Started

    • Getting Started
    • Authentication
    • Architecture & Core Concepts
    • Security & Best Practices
    • Deployment & Hosting Guide

Security & Best Practices

Building financial, payment, and inventory integrations requires uncompromising security standards. Tracepos implements industry-standard defenses to protect merchant data, prevent financial discrepancies, and defend against network attacks.


1. Secret Key Management

Your X-Tracepos-Secret-Key is a confidential credential granting write access to your warehouse inventory and financial records.

Golden Rules

  • Backend Only: Never store or transmit your Secret Key in client-side applications (React/Vue SPAs, iOS/Android mobile apps without an API gateway).
  • Environment Variables: Store keys in environment variables (process.env, .env, AWS Secrets Manager, Azure Key Vault).
  • Key Rotation: Rotate your API keys every 90 to 180 days from the Tracepos dashboard (Settings → API Keys). When rotating, generate a new key pair and test before revoking the legacy key.

2. Idempotency Keys: Preventing Duplicate Charges

In retail and POS terminal environments, network drops and cellular disconnects are common. If a request times out, retrying a POST /sales or POST /payments request without idempotency could result in:

  • A customer being charged twice.
  • Inventory being deducted twice.
  • Duplicate accounting entries in the general ledger.

Implementation

Always send a client-generated unique identifier in the unique_id or Idempotency-Key header:

POST /api/v1/public/sales HTTP/1.1
Host: app.tracepos.net
X-Tracepos-Public-Key: YOUR_PUBLIC_KEY
X-Tracepos-Secret-Key: YOUR_SECRET_KEY
Idempotency-Key: pos-term-01-tx-99482716
Content-Type: application/json

{
  "user_id": "cust_123",
  "order_date": "2026-09-30",
  "invoice_number": "INV-TX-99482716",
  "product_items": [...]
}

If Tracepos receives a subsequent request with the same invoice_number or idempotency token within 24 hours, it returns the previously processed transaction without re-executing stock deductions or ledger entries.


3. HMAC-SHA256 Request Signing (Optional High-Security Mode)

For high-volume financial aggregators and unattended POS kiosks, Tracepos supports request signing via HMAC-SHA256 using your key's hmac_secret:

Signature Recipe

  1. Construct the canonical string: TIMESTAMP.METHOD.PATH.REQUEST_BODY
  2. Compute the HMAC-SHA256 hash using your hmac_secret.
  3. Transmit the signature in X-Tracepos-Signature and timestamp in X-Tracepos-Timestamp.

Node.js Example

const crypto = require('crypto');

function signRequest(method, path, bodyString, hmacSecret) {
  const timestamp = Math.floor(Date.now() / 1000);
  const payload = `${timestamp}.${method.toUpperCase()}.${path}.${bodyString}`;
  const signature = crypto
    .createHmac('sha256', hmacSecret)
    .update(payload)
    .digest('hex');

  return {
    'X-Tracepos-Timestamp': timestamp.toString(),
    'X-Tracepos-Signature': signature,
  };
}

4. Replay Attack Defense

Tracepos rejects requests where the client timestamp drifts by more than 300 seconds (5 minutes) from server UTC time. Ensure your server or POS terminal hardware synchronizes with NTP (Network Time Protocol) servers.


5. Webhook Signature Verification

When Tracepos sends webhook notifications to your application (e.g. sales.created), your server must verify the payload signature before processing:

Tracepos sends:

  • Authorization: Bearer <WEBHOOK_SECRET>
  • X-Tracepos-Signature: <HMAC_SHA256_HEX>

Python Webhook Verifier

import hmac
import hashlib

def verify_tracepos_webhook(raw_payload_bytes: bytes, signature_header: str, webhook_secret: str) -> bool:
    expected_signature = hmac.new(
        webhook_secret.encode('utf-8'),
        raw_payload_bytes,
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(expected_signature, signature_header)

6. Rate Limiting & Graceful Backoff

When making bulk requests (e.g. catalog ingestion or batch sync), honor the HTTP 429 Too Many Requests status:

Recommended Backoff Pattern

When a 429 is encountered:

  1. Inspect the Retry-After header (seconds).
  2. If Retry-After is absent, apply Exponential Backoff with Full Jitter: $$\text{Sleep} = \text{random}(0, \min(\text{MAX_BACKOFF}, \text{BASE} \times 2^{\text{attempt}}))$$
  3. Do not immediately retry in a tight loop.
async function requestWithRetry(fn, maxRetries = 4) {
  let attempt = 0;
  while (attempt < maxRetries) {
    try {
      return await fn();
    } catch (err) {
      if (err.response && err.response.status === 429) {
        attempt++;
        const retryAfter = parseInt(err.response.headers['retry-after'] || '2', 10);
        const jitter = Math.random() * 1000;
        const delay = (retryAfter * 1000) + jitter;
        console.warn(`Rate limited. Backing off for ${delay}ms (attempt ${attempt}/${maxRetries})`);
        await new Promise(res => setTimeout(res, delay));
      } else {
        throw err;
      }
    }
  }
}
Prev
Architecture & Core Concepts
Next
Deployment & Hosting Guide