Skip to content

Security & Best Practices

Follow these guidelines to build a secure, reliable, and maintainable integration with the iorder POS system.

Always Use HTTPS in Production

The iorder API transmits API keys and order data. In production, always connect over HTTPS. Never fall back to HTTP. If you are testing against a staging environment that does not use HTTPS, ensure it is isolated from production networks.

Verify TLS in your HTTP client:

bash
curl -v -H "api_key: abc123-def456-ghi789" \
  "https://{host}/orders/location"

Check that the response includes SSL certificate verify ok or equivalent.

Store API Keys Securely

  • Never hardcode API keys in source code, configuration files that are checked into version control, or client-side code.
  • Use environment variables or a secrets manager:
    bash
    export IORDER_API_KEY="abc123-def456-ghi789"
  • On Kubernetes, use a Kubernetes Secret.
  • On cloud platforms, use the platform's secrets manager (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault).
  • In CI/CD pipelines, inject the API key at runtime rather than storing it in pipeline configuration.

Example: Loading API key from environment (Node.js):

javascript
const apiKey = process.env.IORDER_API_KEY;
if (!apiKey) {
  throw new Error("IORDER_API_KEY environment variable is not set");
}

Example: Loading API key from environment (Java):

java
String apiKey = System.getenv("IORDER_API_KEY");
if (apiKey == null || apiKey.isBlank()) {
    throw new IllegalStateException("IORDER_API_KEY not set");
}

Rotate API Keys Periodically

  • Rotate API keys on a regular schedule (e.g., every 90 days).
  • After rotating, update your deployment configuration with the new key.
  • Keep the old key valid during a transition period to avoid downtime.
  • If a key is compromised, rotate it immediately.

Validate All Data

Although the integration uses polling (not webhooks), you should still treat all data from the API as untrusted:

  • Validate that the API response is valid JSON.
  • Validate that required fields are present and have expected types.
  • Do not pass raw API data directly into database queries (use parameterized queries).
  • Validate string lengths before storing (e.g., order comments, product names).
  • Sanitize data before displaying it in any user-facing UI.

Use Exponential Backoff for Retries

When the API returns a 5xx error or a network error occurs, retry with exponential backoff:

javascript
async function fetchWithRetry(url, options, maxRetries = 5) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      if (response.ok) return response;
      if (response.status < 500) throw new Error(`HTTP ${response.status}`); // Don't retry 4xx
    } catch (err) {
      if (attempt === maxRetries - 1) throw err;
      const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
      await new Promise(r => setTimeout(r, delay));
    }
  }
}
  • Minimum backoff: 1 second
  • Maximum backoff: 30-60 seconds
  • Jitter: Add random jitter to avoid thundering herd
  • Retry only on: 5xx errors, network errors, timeouts
  • Do not retry on: 4xx errors (except 429 if you implement rate limiting)

Implement Idempotency for Status Updates

The PUT /orders/{orderId}/statusupdate endpoint is not idempotent. To prevent duplicate updates:

  • Maintain a local set or database table of processed order numbers.
  • Before sending a status update, check if you have already processed the order.
  • After a successful status update, record the order number as processed.
  • On application restart, initialize the processed set from your local persistence.

Log All API Interactions

Log every API request and response for debugging and auditing:

javascript
function logApiCall(method, url, status, body) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    method,
    url,
    status,
    body: sanitize(body), // Remove sensitive data before logging
  }));
}

What to log:

  • Request method, URL, and timestamp
  • Response status code
  • Response body (or a truncated version)
  • Error details when failures occur
  • SSE connection events (connect, disconnect, reconnect)

What NOT to log:

  • API keys (mask them: abc1****789)
  • Full credit card numbers or payment details
  • Personal data beyond what is necessary

Monitor SSE Connection Health

The SSE stream is the heartbeat of your integration. Monitor it closely:

  • Track the number of SSE reconnections per hour.
  • Alert if reconnections exceed a threshold (e.g., more than 10 per hour).
  • Track the time between the last keepalive event and a connection drop.
  • Log every SSE connection state change (connected, disconnected, error).
  • If running in a containerized environment, expose SSE health as a Prometheus metric or health check endpoint.

Example metric:

text
# HELP iorder_sse_connection_status Current SSE connection status (1=connected, 0=disconnected)
# TYPE iorder_sse_connection_status gauge
iorder_sse_connection_status{location="Main Floor"} 1

# HELP iorder_sse_reconnects_total Total number of SSE reconnections
# TYPE iorder_sse_reconnects_total counter
iorder_sse_reconnects_total{location="Main Floor"} 3

Handle Network Timeouts Appropriately

Set explicit timeouts for all HTTP operations:

OperationRecommended Timeout
Regular API calls (GET, PUT, POST)30 seconds
SSE connection65+ seconds (must be longer than keepalive interval)
DNS resolution5 seconds
Connection establishment10 seconds

Example (Node.js with fetch):

javascript
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000);

try {
  const response = await fetch(url, {
    headers: { api_key: apiKey },
    signal: controller.signal,
  });
  // ...
} finally {
  clearTimeout(timeout);
}

Rate Limit Your Own Polling

Even though the server does not currently enforce rate limiting, you should self-limit:

  • SSE-driven polling: Only poll when the SSE stream sends a message event. This is the primary polling trigger.
  • Fallback interval polling: Poll at intervals no shorter than 5 seconds as a safety net.
  • No busy loops: Never poll in a tight loop (e.g., every 100ms). The server is shared by multiple clients.

Prefer SSE-Driven Polling Over Interval-Only Polling

The system is designed for SSE-driven polling. Interval-only polling:

  • Increases server load unnecessarily.
  • Delays order processing (you might poll just before an order arrives, then wait the full interval).
  • Wastes network and compute resources.

Recommended approach:

  1. Open a persistent SSE connection.
  2. Poll immediately when a message event arrives.
  3. Poll at a 5-second interval as a fallback (in case SSE messages are missed).
  4. If SSE reconnects after a drop, resume SSE-driven polling and reduce fallback polling.

Test Error Scenarios

Before deploying to production, test your integration against these scenarios:

  • Invalid API key
  • Expired API key
  • Server restart (SSE reconnection)
  • Network disconnection (SSE reconnection with offset)
  • Concurrent order processing (two clients polling the same location)
  • Invalid state transitions
  • Large payload (many order rows)
  • Slow network (timeout handling)
  • Missing/null fields in API responses

A robust integration handles all of these without crashing, leaking resources, or corrupting data.

Next Steps

  • Review the Error Handling chapter for detailed error scenarios
  • Refer to the Overview for the complete integration flow