Patterns
Idempotency key generation
UUID v4 vs hash(payload + tenant + timestamp)
An idempotency key guarantees a retried POST executes the action only once. Two strategies. UUID v4: generated client-side, stored in `Idempotency-Key` header. Pro: trivial, fully random. Con: client must persist the key between retries (otherwise new retry = new key = duplicate). Deterministic hash: `sha256(tenant_id || payload_canonical || time_window)` where `time_window` is e.g. timestamp rounded to 5 minutes. Pro: no client storage needed, two identical requests generate the same key. Con: collision risk if two legitimate requests have the exact same payload in the same window. Scell.io accepts UUID v4 from the client (recommended) and stores the response for 24h. If the header is absent, Scell.io generates an internal hash for critical endpoints (invoice creation, balance reload).
Key facts
- UUID v4: recommended, client persists key between retries
- Deterministic hash: sha256(tenant_id || canonical_payload || time_bucket)
- Scell.io header: `Idempotency-Key: <uuid>`
- Server storage: 24h, response replayed identically on retry
- Critical endpoints: POST /invoices, POST /signatures, POST /balance/reload
Code example
import { randomUUID, createHash } from 'node:crypto';
// Stratégie 1 : UUID v4 (recommandé)
const idempotencyKey = randomUUID();
await persistKey(operationId, idempotencyKey); // store for retries
// Stratégie 2 : Hash déterministe (fallback sans stockage client)
function deterministicKey(tenantId: string, payload: object): string {
const canonical = JSON.stringify(payload, Object.keys(payload).sort());
const timeBucket = Math.floor(Date.now() / (5 * 60 * 1000));
return createHash('sha256')
.update(`${tenantId}|${canonical}|${timeBucket}`)
.digest('hex');
}
await fetch('https://api.scell.io/api/v1/invoices', {
method: 'POST',
headers: { 'Idempotency-Key': idempotencyKey, 'X-API-Key': process.env.SCELL_KEY! },
body: JSON.stringify(payload),
});