Security question or disclosure? Email us at sf-core-org-support-calmony-dd@saas-factory.ai — we respond to every message.
Security question or disclosure? Email us at sf-core-org-support-calmony-dd@saas-factory.ai — we respond to every message.
Calmony DD handles payer bank details, live BACS collections, and client-money forwarding for UK letting agencies. Every control below is implemented and verifiable — not aspirational. We tell you what we do, not what we intend to do.
Payer bank account numbers are encrypted with AES-256-GCM before they reach the database. A dedicated PII vault manages keys; decryption is logged every time a full account number is accessed.
Every domain table carries an org_id column with PostgreSQL Row-Level Security enforced at the database layer. An authenticated session for Agency A cannot read or mutate a single row belonging to Agency B — enforced by policy, not application logic alone.
Shell-issued API keys are scoped to a single agent organisation. A key cannot query another org's payers, mandates, or collections — regardless of what the caller sends in the request body.
[ DATA PROTECTION / PII-VAULT ]
Sort codes and account numbers pass through encryptPii() immediately on write. The plaintext value is never persisted — only the AES-256-GCM ciphertext and a last-4 display stub. When a collection workflow needs the full account number for a Modulr submission, decryptPii() is called server-side inside a Temporal activity, and the access is audit-logged automatically via shell.logAudit.
Account numbers stored only as ciphertext (AES-256-GCM, unique IV per field)
Last-4 shown in UI by default; full value decrypted only for Modulr submission
Every decrypt writes an audit entry with user, org, and timestamp
Dual-write migration underway to encrypt name, address, city, and postcode

[ CYCLE / REAL-TIME ]
[ HOW IT WORKS / MONEY-MOVEMENT ]
A payer's bank details are validated, encrypted, and sent to Modulr as a new Direct Debit mandate. The AUDDIS workflow polls for a bank confirmation over up to 3 BACS working days, then activates the mandate — or surfaces a rejection with the reason code.
The daily collection scheduler picks up mandates due within the BACS working-day window, checks submission cutoffs using the built-in BACS calendar, and submits each collection to Modulr with an idempotency key. The collection status tracks every stage: scheduled → submitted → in clearing → settled or returned.
On settlement, funds are swept to a Griffin holding account and a hold record is created with a release date of collection day + 5 BACS working days. If an ARUDD return or indemnity claim arrives in that window, funds are recalled automatically. After Day +5 with no claim, the hold is released.
ForwardFundsWorkflow computes the releasable amount — sum of released holdings minus any reserve top-up and pending indemnity holds — then calls Griffin to transfer the net figure to the agent's client account. A forwarding statement PDF is generated and emailed automatically.
[ RECONCILIATION / DAILY ]
A daily reconciliation cron pulls the previous day's Modulr settlement file and Griffin holding-account transactions, then matches each collected item against the expected amount. Any mismatch — missing, unexpected, or wrong amount — goes straight to the exceptions queue with severity flagged for the agent.
Double-entry ledger enforces sum(debits) = sum(credits) per transaction
Nightly integrity cron verifies every completed forwarding has a matched ledger pair
Orphaned ledger entries surface as exception_triage rows automatically
Agent receives a reconciliation mismatch email with count and total pence when mismatches exist

[ EXCEPTION-HANDLING / TRIAGE ]
Automated detectors surface stuck workflows, unmatched settlements, stale collections, and indemnity claims before they become problems.

ARUDD returns handled automatically Return codes decoded against the full BACS reason-code catalogue. Retry-eligible returns are re-submitted on the next available BACS processing date; the agent is notified with a plain-English reason.
Stuck-workflow detector runs daily Mandates stuck in pending_auddis beyond 5 BACS working days and collections stuck in submitted beyond 4 create exception records with deep links to the entity.
Indemnity claims escalate on a timer Claims unreviewed after 3 BACS working days auto-escalate to critical severity. A second escalation at 5 days alerts the platform admin.
Provider webhook events are re-dispatched Unprocessed webhooks older than 1 hour are automatically re-attempted on an hourly sweep — no manual intervention required to recover from a momentary worker outage.
[ CONTROLS / IMPLEMENTED ]
179 features shipped. Below are the security and compliance controls that are running now — not a roadmap.
Row-Level Security policies on every domain table. Tenant data isolation enforced at the database layer, tested by a dedicated isolation test suite across every tRPC procedure and REST endpoint.
PII decryption events are audit-logged via shell.logAudit with user, org, and timestamp. Audit coverage is being extended to all financial mutations — indemnity approve, forwarding trigger, mandate cancel.
Every inbound Modulr webhook is verified against an HMAC signature before processing. Events are deduplicated on event ID before dispatch to prevent double-processing.
Every Modulr and Griffin money-moving call carries an idempotency key. Temporal workflow restarts and operator retries cannot trigger a second BACS submission for the same collection.
Every outbound Modulr and Griffin API call and inbound webhook is stored in full — request, response, status, duration — with secrets redacted. Searchable and replayable from the admin console.
A seeded BACS working-day calendar — loaded from Pay.UK's published schedule — governs every submission cutoff, settlement date, and clawback calculation. No ad-hoc date arithmetic.
[ FORWARDING / AUDIT-READY ]
Every forwarding from the Griffin holding account to an agent's client account generates a PDF statement: transfer amount, each included collection with payer, date and BACS reference, reserve top-up, and any clawbacks. The statement is attached to the forwarding email and available for download from the forwarding detail page.
Double-entry ledger is the single source of truth for all statement figures
Monthly per-agent statements available as Excel and PDF from the Reports section
Reserve balance and movement included in every statement
Forwarding history sortable and downloadable from the forwardings dashboard

[ KNOWN GAPS / HONEST DISCLOSURE ]
Trust is built on honesty about gaps as much as confidence in controls. The following are tracked and in progress.
PII encryption phase 2 in progress Phase 1 added encrypted columns for payer name and address. Phase 2 — backfilling existing rows and dropping plaintext columns — is the active migration work.
GDPR data-retention automation not yet live Retention periods are defined in policy. Automated purge workflows for long-lived PII tables are pending implementation.
SOC 2 audit trail being extended PII decryption is logged. Audit coverage is being added to all financial mutations — indemnity approve/reject, forwarding trigger, mandate cancel — and to sort-code-bearing queries.
Step-up authentication for high-risk operations pending Indemnity claim approval and bulk forwarding trigger currently require only a valid session. Step-up MFA for these operations is on the security roadmap.
[ FAQ / SECURITY ]
Calmony DD is currently in early access. If you manage 50–500 properties and want a Direct Debit service built around the full BACS lifecycle — not bolted onto a generic payments API — join the waitlist.