EDI Configuration
Core
| Variable | Default | Purpose |
|---|---|---|
EDI_BIND_ADDR | 0.0.0.0:8480 | HTTP bind address |
EDI_API_TOKEN | β | Admin bearer token (full access to all /v1 routes). Required |
EDI_AUTH_DISABLED | false | Dev-only escape hatch for the above |
EDI_DATA_DIR | ./data | Per-instance stores (document log; file-backed coordination stores when DATABASE_URL is unset) |
EDI_CORS_ORIGINS | β | Comma-separated allowed origins (* for any); unset means same-origin only |
EDI_RATE_LIMIT_RPM | 600 | Per-client request budget on /v1 routes (0 disables) |
EDI_TRUSTED_PROXY_HOPS | 0 | Trusted reverse proxies in front; 0 ignores X-Forwarded-For |
DATABASE_URL | β | Postgres for all shared coordination state; unset means a per-instance file backend |
StateSet integration
| Variable | Default | Purpose |
|---|---|---|
STATESET_SEQUENCER_URL | https://api.sequencer.stateset.com/v1 | VES sequencer |
STATESET_API_KEY | β | Enables outbox flushing, automatic and manual |
STATESET_SYNC_SERVER_URL | β | Optional second event sink: sync-server base URL |
STATESET_EDI_WEBHOOK_SECRET | β | HMAC secret for sync-server event delivery (required with the URL). See event delivery signing β this path binds a timestamp, unlike alert webhooks |
STATESET_EDI_DEFAULT_TENANT_ID | β | Legacy fallback tenant for pre-stamping events; leave unset in multi-tenant deployments |
EDI_FLUSH_SECONDS | 30 | Automatic outbox flush interval (0 disables) |
Event delivery signing
Two signing schemes are in play, and they are not interchangeable.| Path | Header | Signed over |
|---|---|---|
| Operational alerts, lead capture | X-StateSet-Signature | the raw body |
| Sync-server event delivery | x-stateset-edi-signature + x-stateset-edi-timestamp | "{timestamp}.{body}" |
sha256=<hex> HMAC-SHA256 values.
The event-delivery path binds the timestamp into the signed input to prevent replay. A
receiver that verifies it as body-only will reject every delivery. Reconstruct the input as
timestamp + "." + rawBody, using the timestamp from x-stateset-edi-timestamp.import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyEdiDelivery(rawBody, headers, secret) {
const ts = headers['x-stateset-edi-timestamp'];
const header = headers['x-stateset-edi-signature'] ?? '';
if (!ts || !header.startsWith('sha256=')) return false;
const signed = Buffer.concat([Buffer.from(`${ts}.`), rawBody]);
const expected = createHmac('sha256', secret).update(signed).digest();
const provided = Buffer.from(header.slice(7), 'hex');
return provided.length === expected.length && timingSafeEqual(provided, expected);
}
Transports
See Transports for the file, SFTP, AS2, and AS1 variable groups.| Variable | Default | Purpose |
|---|---|---|
EDI_INBOUND_DIR / EDI_OUTBOUND_DIR | β | File/VAN directory transport |
EDI_POLL_SECONDS | 10 | Inbound directory poll interval |
EDI_SETTLE_SECONDS | 2 | Inbound files must be unmodified this long before pickup |
EDI_VAN_ROOT | β | Root directory for file-backed VAN mailboxes |
EDI_SFTP_* | β | Native SFTP-client mailbox |
EDI_DELIVERY_POLL_SECONDS | 60 | AS2 delivery worker scan interval (0 disables) |
EDI_MDN_TIMEOUT_HOURS | 24 | Async-MDN deadline before a delivery is re-driven |
Cryptography and key custody
| Variable | Default | Purpose |
|---|---|---|
EDI_AS2_KEY_FILE / EDI_AS2_CERT_FILE | β | Our S/MIME signing identity (PEM) for AS2 |
EDI_AS2_PERSIST_IDENTITY | false | Persist runtime identity rotations to the state backend so they survive restarts and reach all replicas. Trade-off: puts key PEM in the DB or data dir |
EDI_AS2_REQUIRE_VERIFIED_INBOUND | true | Fail closed on inbound AS2: every message must be signed and verified against the partnerβs certificate |
EDI_SIGNING_KEY_COMMAND / EDI_SIGNING_PUBLIC_KEY | β | HSM/KMS key custody: delegate event signing to an external command so the private key never enters the process |
EDI_SIGNING_STRICT | true | Fail closed: block an event if signing fails rather than emit it unsigned |
EDI_SIGNING_KEY / EDI_SIGNING_KEY_ID / EDI_SIGNING_DISABLED | auto / 1 / false | VES event-signing key (hex seed; auto-generated and persisted when unset), its sequencer key id, and the off switch |
EDI_CERT_EXPIRY_WARN_DAYS | 21 | Warn when the AS2 certificate is within N days of expiry |
EDI_GS1_COMPANY_PREFIX | β | Our GS1 company prefix; required to mint SSCC-18 shipping labels |
EDI_SIGNING_STRICT=false adopts an explicit fail-open posture β events can be emitted
unsigned. Use it only in development.Alerting
| Variable | Default | Purpose |
|---|---|---|
EDI_ALERT_WEBHOOK_URL | β | Slack-compatible webhook. Validated at startup: a private/loopback URL fails fast unless EDI_ALERT_WEBHOOK_ALLOW_PRIVATE=true |
EDI_ALERT_WEBHOOK_SECRET | β | When set, each alert POST carries an X-StateSet-Signature: sha256=<hex> HMAC of the body |
EDI_ALERT_THROTTLE_SECONDS | 300 | Storm damper: repeats of the same alert kind and partner within the window are counted, not re-sent. 0 disables |
EDI_ACK_SLA_HOURS | 24 | Outbound docs un-acked (no 997) past this age are overdue |
EDI_MAIL_API_URL / EDI_MAIL_API_KEY / EDI_MAIL_FROM | β | stateset-mail connector for email alerts and AS1 |
EDI_ALERT_EMAIL_TO | β | Comma-separated recipients for operational alert emails |
Back-end adapters
| Variable | Purpose |
|---|---|
EDI_SHOPIFY_* / EDI_NETSUITE_* | Credentials and endpoints for explicitly invoked live adapters. Catalog planning works without them. |
Retention
All*_RETENTION_DAYS sweeps run hourly on the cluster monitor leader. 0 (default) keeps
rows forever. See Operations.
| Variable | Prunes |
|---|---|
EDI_DOCUMENT_RETENTION_DAYS | Document and raw-wire detail JSONL audit logs |
EDI_DELIVERY_RETENTION_DAYS | Terminal AS2/AS1 delivery rows, payloads included |
EDI_USAGE_RETENTION_DAYS | Usage-ledger rows |
EDI_SEEN_RETENTION_DAYS | Interchange-dedup entries (Postgres backend only) |
EDI_DEADLETTER_RETENTION_DAYS | Dead-lettered outbox events not replayed within the window |