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) |
# Core
EDI_BIND_ADDR=0.0.0.0:8480
EDI_API_TOKEN=edi_prod_9f2c…
EDI_DATA_DIR=/var/lib/stateset-edi
EDI_RATE_LIMIT_RPM=600
# One trusted proxy in front, so client IPs are read from X-Forwarded-For
EDI_TRUSTED_PROXY_HOPS=1
# Shared coordination state; without this each instance keeps its own files
DATABASE_URL=postgres://edi:edi@db.internal:5432/stateset_edi
# StateSet integration
STATESET_API_KEY=sk_live_…
STATESET_SEQUENCER_URL=https://api.sequencer.stateset.com/v1
STATESET_EDI_WEBHOOK_SECRET=whsec_…
EDI_FLUSH_SECONDS=30
DATABASE_URL is not optional once you run more than one instance. Unset, each
instance keeps coordination state in its own EDI_DATA_DIR, so control-number
allocation and replay dedup are per-instance — two instances will happily issue
the same control number to the same partner.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 |
Next steps
Transports
The AS2, SFTP and VAN settings these variables switch on.
Operations
Reconciliation, evidence bundles and deductions.
Transaction sets
The documents that flow once this is configured.
MCP server
Driving the gateway from an agent.