Authentication
The publisher and subscriber must agree on the same auth mode. SYNC_AUTH_MODE (hmac or token) is read by both bundles from src/shared/config.ts, and the modes do not cross-accept: a token-mode subscriber rejects HMAC-signed requests and vice-versa.
| Mode | Headers | Notes |
|---|---|---|
hmac (default) | x-sync-timestamp, x-sync-signature | Per-request HMAC-SHA256 of <timestamp>.<rawBody> keyed with the shared secret. Replay-protected by timestamp tolerance plus an in-memory exact-request replay cache. Every retry re-signs with a fresh timestamp. |
token | x-sync-token | Static shared-secret bearer token. Simpler; use only over TLS or another protected transport. Delivery remains idempotent via eventId / entityRevision, but token mode does not reject request replay at the auth boundary. |
Requests must use Content-Type: application/json. Unsupported Content-Encoding values are rejected before application.
HMAC mode (recommended)
The signature is HMAC_SHA256(sharedSecret, "<timestamp>.<rawBody>"), hex-encoded.
<timestamp>is a Unix-millisecond integer sent inx-sync-timestamp.<rawBody>is the exact bytes sent on the wire. The publisher signs the JSON string it serialized. The subscriber reads n8n's globalrawBodyReadervalue (req.rawBody) when available, otherwise it reads the unread request stream.- Fail-closed parsing — in HMAC mode the subscriber does not verify a re-serialized
req.body. If only a pre-parsed body remains, the request fails closed because the exact signed bytes are no longer available. If bothreq.rawBodyandreq.bodyexist,req.bodyis ignored and the raw bytes are parsed and applied. - Replay protection — the subscriber rejects a request when the timestamp is outside
SYNC_SIGNATURE_TOLERANCE_MS(default300000ms = 5 minutes). It also rejects an exact re-send of the same signed request while an identical request is in flight and after a successful application while the process-local replay cache remembers it. - Re-signing on retry — every delivery attempt (including retries inside
sendSyncEvent) generates a fresh<timestamp>.<rawBody>pair and re-signs it. A retried request never reuses a previous signature. - Retryable failures release reservations — parse, validation, and application failures release the replay reservation so the exact request can be retried.
# publisher
export SYNC_AUTH_MODE=hmac
export SYNC_SHARED_SECRET=<32+-char-random-string>
# subscriber — must be identical
export SYNC_AUTH_MODE=hmac
export SYNC_SHARED_SECRET=<32+-char-random-string>
Why raw bytes matter
A JSON body that round-trips through JSON.parse(req.body) and JSON.stringify(...) is not byte-identical to the original in general (key reordering, whitespace). HMAC over a re-serialized body could verify bytes different from what is applied. The subscriber therefore authenticates the exact raw bytes first, then parses and applies those same bytes. See src/shared/body.ts and src/shared/auth.ts for the implementation.
Token mode
Static shared-secret bearer. There is no signing, no replay protection, no timestamp. Use it only when the publisher → subscriber hop is over mTLS or another protected transport.
# publisher
export SYNC_AUTH_MODE=token
export SYNC_SHARED_SECRET=<any-shared-string>
# subscriber — must be identical
export SYNC_AUTH_MODE=token
export SYNC_SHARED_SECRET=<any-shared-string>
The subscriber compares x-sync-token against the shared secret using a constant-time equality to avoid timing side-channels.
Gotchas
- Modes do not cross-accept. A token-mode subscriber rejects hmac-signed requests, and an hmac-mode subscriber rejects token-bearing requests. Both sides must agree on
SYNC_AUTH_MODE. SYNC_SHARED_SECRETis required in both modes — it is the HMAC key in hmac mode and the bearer token in token mode.- Token mode may reuse parsed JSON. Authentication does not depend on raw bytes in token mode, but the subscriber still enforces JSON content type and request size limits.
- Replay rejection is process-local. HMAC replay cache entries are not shared across OS processes and do not survive restart. Durable ordering still makes stale re-delivery a no-op after cache expiry or restart.
Reference
- Architecture — where authentication sits in the request pipeline.
- Environment variables —
SYNC_AUTH_MODE,SYNC_SHARED_SECRET,SYNC_SIGNATURE_TOLERANCE_MS.