Skip to content

Redis key registry ​

Status ​

Accepted for MVP documentation.

Date ​

2026-06-25

Context ​

This registry defines Redis key naming and payload boundaries for MyHoreca Pay MVP. Redis stores temporary/runtime state only. PostgreSQL remains the source of truth for permanent configuration, business records, and audit history.

Key naming convention ​

Canonical Redis key pattern:

text
mhpay:{env}:{tenantScope}:{domain}:{purpose}:{id}

Fields:

FieldAllowed valuesRule
envdev, test, staging, prodEnvironment partition.
tenantScopeorg:{organizationId}, outlet:{outletId}, platform, user:{userId}Use tenant ownership scope when it exists. Use user:{userId} only where scope is not tenant-owned.
domainauth, invite, syrve, mfa, throttle, job, contextRuntime domain.
purposeConcrete runtime purposeExamples: object, active-by-org, runtime-token, challenge, counter.
idUUID, token hash, recipient hash, correlation id, connection idMust not be a raw secret.

Forbidden in Redis key names:

  • plaintext invitation token;
  • Redis password;
  • Syrve/iiko password, API key, refresh secret, or connection secret;
  • raw bank line;
  • full account number;
  • private customer payload.

TTL policy ​

Runtime objectTTL
Invitations3 hours.
MFA/session challengesShort TTL, target placeholder 5-15 minutes; exact value is a future implementation decision.
Auth/session temporary stateSession-bound TTL; exact duration is a future implementation decision.
Active contextSession-bound TTL.
Throttling countersWindow-based TTL matching the throttle window.
Background job cacheBounded TTL; not a system of record. Exact duration is a future implementation decision.
Syrve/iiko runtime token/sessionTTL less than or equal to upstream token/session expiry.

Key registry ​

KeyKey patternValue typeOwner/scopeTTLPersistence expectationAllowed payloadForbidden payloadPostgreSQL audit counterpartCleanup/cancel behavior
Invite objectmhpay:{env}:org:{organizationId}:invite:object:{inviteId}JSON objectOrganization, optional Outlet in value3 hoursAOF/RDB may retain until TTL; still runtime-onlyInvite id, storage organization id, organization id, optional location id, email hash, normalized email (runtime accept flow), role code, created-by user id, timestamps, isActive, correlation id, token hashPlaintext invitation token, secrets, raw bank datainvite.created, invite.cancelled, invite.acceptedExpire by TTL; delete on cancel or accepted; fail closed if missing
Invite active index by organizationmhpay:{env}:org:{organizationId}:invite:active-by-org:{organizationId}Set or sorted set of invite idsOrganizationMax 3 hours per member/object; cleanup may be opportunisticRuntime index onlyInvite ids, expiry timestamps if sorted setFull invite payload, plaintext tokens, raw emailSame invite lifecycle audit eventsRemove invite id on cancel/accepted; remove expired members during cleanup/list
Invite active index by recipient hashmhpay:{env}:org:{organizationId}:invite:active-by-email-hash:{emailHash}Set or string/list of invite idsOrganization plus recipient hashMax 3 hours per member/object; cleanup may be opportunisticRuntime lookup index onlyInvite ids, hashed normalized lowercase email, expiry metadataRaw email unless explicitly approved later, plaintext token, secretsSame invite lifecycle audit eventsDelete/update on cancel/accepted; clean expired invite ids
Runtime Syrve/iiko token/sessionmhpay:{env}:org:{organizationId}:syrve:runtime-token:{connectionId}JSON object or encrypted runtime blobOrganization connectionTTL <= upstream expiryRuntime-only; may survive restart until TTL if persistence worksRuntime access token/session state, upstream expiry, connection id, correlation idPermanent credentials, API keys, passwords, refresh secrets, connection secrets, token payload in auditOptional token lifecycle metadata audit without token valueDelete on logout/connection disable/token invalidation; expire by TTL
Runtime Syrve/iiko token refresh lockmhpay:{env}:org:{organizationId}:syrve:runtime-token-refresh-lock:{connectionId}String lock owner idOrganization connectionShort lock TTL, currently 15 secondsRuntime-only; safe against deadlock by TTLRandom lock owner/correlation metadata onlyPasswords, password hashes, API keys, permanent secrets, runtime token/session valueCritical reconnect failure audit after retry exhaustion onlyDelete by lock owner on release; expire by TTL
MFA challenge/sessionmhpay:{env}:user:{userId}:mfa:challenge:{challengeId}JSON objectUser runtime scopeShort TTL, target placeholder 5-15 minutesRuntime-onlyChallenge id, user id, hashed challenge secret or verifier, created/expiry timestamps, attempt counter, correlation idPlain challenge secret where hash/verifier is possible, passwords, recovery codesMFA security audit when policy requiresExpire by TTL; delete on successful verification or cancellation
Auth/session temporary statemhpay:{env}:user:{userId}:auth:session-state:{sessionStateId}JSON objectUser runtime scopeSession-bound TTLRuntime-onlySession state id, nonce/hash, created/expiry timestamps, correlation idPasswords, raw tokens if hash is possible, permanent user dataLogin/session security audit when policy requiresExpire by TTL; delete after exchange/logout/invalidation
Active contextmhpay:{env}:user:{userId}:context:active:{sessionId}JSON objectUser runtime scope with selected organization/OutletSession-bound TTLRuntime-onlyUser id, organization id, optional Outlet id, active role/scope hints, timestamp, correlation idPermissions as permanent truth, secrets, raw bank dataNone required; audit business actions separatelyExpire with session; delete on logout/context reset
Throttling countermhpay:{env}:{tenantScope}:throttle:counter:{windowId}Counter or hashPlatform, organization, user, or IP-derived scope as implementation requiresWindow-based TTLRuntime-onlyCounter value, window start/end, scope id, action nameRaw credentials, raw IP if hashed/salted policy is chosen later, payload dataSecurity audit only for threshold events when policy requiresExpire by TTL; reset only by policy-authorized admin/system operation
Background job cachemhpay:{env}:{tenantScope}:job:cache:{jobId}JSON object/hashOwning tenant scope or platform for system jobsBounded TTL; exact duration future implementation decisionRuntime cache onlyProgress percent, last heartbeat, non-sensitive status, correlation idDurable job source of truth, outbox payload, secrets, raw bank lines, full account numbersDurable job/audit records outside RedisExpire by TTL; delete after completion/cancellation; do not delete durable records

Invitation JSON schema ​

Documentation schema for invite:object value:

json
{
  "inviteId": "uuid-v7",
  "storageOrganizationId": "uuid-v7",
  "organizationId": "uuid-v7|null",
  "locationId": "uuid-v7|null",
  "normalizedEmail": "lowercase-email-for-runtime-accept",
  "emailHash": "sha256-lowercase-email",
  "tokenHash": "sha256-invite-token",
  "roleCode": "OrganizationAdmin|Accountant|Viewer|...",
  "createdByUserId": "uuid-v7",
  "createdAtUtc": "ISO-8601",
  "expiresAtUtc": "ISO-8601",
  "correlationId": "string",
  "isActive": true,
  "expiresAtUnixSeconds": 1782483330
}

Rules:

  • Store token hash or a derived lookup key, not a plaintext invitation token.
  • Do not include plaintext invitation token in JSON payload.
  • Keep recipient email in normalized runtime form only when needed for accept flow; use emailHash for audit/index metadata.
  • Do not include raw bank lines, full account numbers, Syrve/iiko credentials, Redis passwords, or private payloads.
  • roleCode must use canonical English role names.
  • locationId may be null when the invite is organization-scoped; it maps to Outlet scope in product terminology.
  • Runtime invite indexes use sorted sets for active-by-org and active-by-email-hash, with invite id members formatted as UUID "N" and expiry timestamps as scores.
  • A RediSearch JSON index is initialized for invite object keys under the mhpay:{env}:... namespace for lookup/list readiness.

Runtime Syrve/iiko token key ​

Canonical key pattern:

text
mhpay:{env}:org:{organizationId}:syrve:runtime-token:{connectionId}

Rules:

  • Store only short-lived runtime token/session state.
  • TTL must be less than or equal to upstream token/session expiry.
  • If upstream expiry is unknown, TTL is bounded by Iiko.RestSessionVerification.SessionCacheSeconds; non-positive values skip Redis persistence.
  • Store payload as typed runtime DTO with connectionId, organizationId, sessionId, created/expiry timestamps, sessionTransport, and optional safe correlation metadata.
  • Protect upstream login/refresh with runtime-token-refresh-lock; lock key and value must not contain token/session/password/secret material.
  • On runtime 403/iiko_forbidden, delete the runtime token key, refresh under lock, and retry. Stop after 7 reconnect failures and write only safe critical failure audit metadata.
  • Permanent Syrve/iiko credentials, API keys, passwords, refresh secrets, and connection secrets are forbidden.
  • Permanent credentials must remain in approved permanent secret storage or PostgreSQL-backed configuration.
  • Runtime token values must not be written to PostgreSQL audit.
  • Credentials rotation must delete this key after saving the new encrypted secret and before testing the connection.

Cleanup requirements ​

  • Cancel invite deletes the invite object key and every active invite index entry.
  • Accept invite deletes or marks inactive the invite object key and every active invite index entry.
  • Syrve credentials rotation deletes the runtime token/session key for the rotated connection; PostgreSQL audit records only safe status metadata.
  • Expired invite indexes must be cleaned opportunistically or by a bounded cleanup job.
  • Redis cleanup must not delete PostgreSQL audit.
  • Missing runtime objects must fail closed unless a future ADR explicitly defines recovery.
  • Background cache cleanup must not remove durable job/outbox/business records.