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:
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):
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):
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:
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:
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:
# 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"} 3Handle Network Timeouts Appropriately
Set explicit timeouts for all HTTP operations:
| Operation | Recommended Timeout |
|---|---|
| Regular API calls (GET, PUT, POST) | 30 seconds |
| SSE connection | 65+ seconds (must be longer than keepalive interval) |
| DNS resolution | 5 seconds |
| Connection establishment | 10 seconds |
Example (Node.js with fetch):
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
messageevent. 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:
- Open a persistent SSE connection.
- Poll immediately when a
messageevent arrives. - Poll at a 5-second interval as a fallback (in case SSE messages are missed).
- 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