> Disclaimer: This content is for educational and reference purposes for software builders only. It does not constitute medical, legal, financial, or regulatory advice. Consult a qualified professional before making decisions based on this content.
Use an Event-Sourced Append-Only KYC Data Model
Rationale: KYC data changes over the life of a customer relationship (document expiry, PEP status change, EDD upgrade), and regulators may ask to reconstruct the customer's KYC state at any historical point in time. Mutable records cannot satisfy this requirement.
Do: Model the KYC record as an event stream: each verification action, status change, and risk assessment is appended as an immutable event with a UTC timestamp, actor ID, and event type. Derive the current state by replaying events. Store a periodic snapshot for query performance.
Don't: Update a single customers row in place to reflect the current KYC status. A single row with status=verified tells you nothing about when verification occurred, who performed it, or what was the state at a disputed date.
Separate Sanctions Screening from Onboarding Flow Latency
Rationale: Sanctions screening (OFAC SDN, EU Consolidated, UN) involves fuzzy matching against large lists. Blocking the onboarding API call for full fuzzy matching introduces latency and degrades user experience.
Do: Run a synchronous fast exact-match check (name hash lookup) during onboarding for immediate blocking of obvious matches. Queue a full fuzzy-match screening job that completes within seconds and writes the result back, placing the account in a pending state until the full result is available. Design the KYC state machine so accounts cannot transact until the full screening result clears.
Don't: Either skip fuzzy matching (causes false negatives - structurally similar names not caught) or block the entire onboarding synchronously on a full fuzzy match across millions of SDN entries.
Store AML Rule Parameters in the Database, Not in Code
Rationale: Compliance officers must be able to adjust AML monitoring thresholds (CTR limit, velocity window, structuring lookback period) in response to regulatory changes or new typologies - without requiring a software deployment.
Do: Persist every rule's threshold, window, and condition in an aml_rules database table. The rules engine reads active rules at startup (or on a configurable interval). A compliance admin UI allows parameter edits with a full audit trail (who changed what, when, prior value).
Don't: Hard-code the $10,000 CTR threshold, the 30-day SAR deadline, or the 20-transaction velocity limit as constants in application code. Even "stable" regulatory thresholds change (e.g., FATF Travel Rule threshold varies by jurisdiction).
Implement Multi-Layer SAR Deadline Notifications
Rationale: Missing the 30-day SAR filing deadline is a BSA violation. A single notification sent at T-30 is insufficient because cases can stall in analyst queues.
Do: Compute the filing deadline at case creation and implement escalating notifications: T-10 to assigned analyst, T-5 to analyst and supervisor, T-2 to analyst, supervisor, and MLRO, with automated escalation if no action is taken. Store all notification events in the case audit log.
Don't: Rely on analysts to track deadlines manually, or send a single reminder email at the time of case creation. Both patterns lead to missed deadlines in high-volume compliance teams.
Never Expose SAR Existence to the Subject
Rationale: Tipping off a SAR subject is a criminal offence under 31 U.S.C. § 5318(g)(2). Software systems must ensure that SAR status never reaches the customer through any API, email, account notification, or support interaction.
Do: Implement a tipping_off_guard flag on all SAR-linked case records. Audit all API responses involving accounts with open SAR cases to ensure no SAR metadata is returned. Train support staff that account restrictions linked to a SAR must be explained only as "account review in progress." Log all access to SAR case data with actor identity.
Don't: Return error messages like "Your account has been flagged for suspicious activity" or "A SAR has been filed." Do not send automated account-restriction emails that coincide with SAR filing.
Use Server-Side UTC Timestamps for All Transaction Events
Rationale: Transaction monitoring rules depend on precise event timing (velocity windows, CTR aggregation by business day). Client-supplied timestamps are untrustworthy and can be manipulated to evade rules.
Do: Assign all transaction timestamps server-side at the moment of API receipt. Use UTC throughout. Store timestamps as TIMESTAMPTZ in PostgreSQL. Preserve the client's submitted timestamp in a separate client_submitted_at field for reconciliation but never use it for rule evaluation.
Don't: Accept the client's timestamp as the authoritative event time, or store timestamps as local time strings without timezone information.
Implement a Retention Hold Mechanism for AML Records
Rationale: GDPR Art. 17 gives EU customers a right to erasure, but BSA and EU AMLD mandate five-year retention of KYC and transaction records. These obligations conflict and must be resolved in software.
Do: Implement a compliance_hold flag on customer and transaction records. Place a hold at the start of the regulatory retention period. GDPR deletion requests trigger a soft-delete that sets a deletion_requested_at timestamp, but the hold prevents hard deletion. A scheduled job removes the hold only when retention_expires_at has passed, then executes the pending deletion.
Don't: Delete KYC records in response to GDPR requests without checking for active retention holds. Equally, retain data indefinitely without implementing the five-year deletion cycle.
Implement Dynamic Linking for SCA-Authenticated Payments
Rationale: Without dynamic linking, a man-in-the-middle attack can intercept a valid SCA code and replay it with a different payment amount or beneficiary. PSD2 RTS explicitly requires dynamic linking. (PSD2 RTS Art. 5, EUR-Lex 2018/389)
Do: Include the payment amount (in minor units) and the payee IBAN in the challenge data before computing the SCA code (FIDO2 challenge or TOTP seed). Verify server-side that the returned code was computed over a challenge that matches the submitted payment details.
Don't: Use a static OTP or session-bound SCA that is not tied to the specific payment amount and payee.
Log Every Consent Grant and Revocation in Open Banking
Rationale: PSD2 requires that consent be explicit, granular, time-limited, and revocable. In the event of a dispute, the consent log is the only evidence that a TPP was authorised to access an account.
Do: Record each consent event (grant, access, refresh, revocation) with: consent ID, TPP identifier, scopes, customer ID, timestamp, IP address, and the access token(s) issued. Provide a customer-facing consent management UI that lists active consents and allows immediate revocation.
Don't: Manage consent only in the TPP's systems without maintaining an independent log at the ASPSP (bank) side. A bank cannot demonstrate PSD2 compliance if it has no consent records.
Validate IBAN Format Before Storing or Processing
Rationale: Malformed IBANs cause payment rejections, AML false-positive alerts, and data quality issues. Validation at entry is cheaper than downstream correction.
Do: Implement MOD-97 IBAN validation (ISO 13616) as a utility function called at every payment form submission and at API boundaries where IBANs are received. Return a clear validation error with the specific failure reason (country code invalid, check digit failed, BBAN length incorrect). Store only validated IBANs.
Don't: Accept IBANs without structural validation, relying on downstream payment processing to reject invalid entries. Downstream rejections arrive too late to be actionable in the user session.
Use Parameterised Sanctions Screening with Documented False-Positive Tuning
Rationale: Over-aggressive fuzzy matching produces large volumes of false-positive sanctions alerts, overwhelming compliance analysts and degrading the effectiveness of genuine matches.
Do: Document the fuzzy-matching algorithm and threshold used (e.g., Levenshtein distance ≤ 2, Jaro-Winkler > 0.85, phonetic Soundex match). Record the match score on every screening result. Track analyst false-positive dispositions over time and use them to calibrate the threshold. Implement a "whitelist" for known false-positive names (e.g., common names identical to SDN entries) with mandatory re-review on whitelist expiry.
Don't: Use only exact-match screening (high false-negative rate) or use very low fuzzy thresholds without analyst capacity to process the resulting alert volume.
Implement AML Risk Scoring as a Multi-Factor, Versioned Model
Rationale: Regulatory examiners expect financial institutions to demonstrate a risk-based approach with documented, auditable scoring criteria. A single risk flag or manual tier is insufficient.
Do: Design a risk scoring model that combines weighted factors: customer type (individual/corporate), jurisdiction risk, product risk, transaction behaviour risk, PEP/sanctions status, and adverse media. Store each factor value, its weight, and the composite score in a risk_assessments table with a model_version field. When the model is revised, re-score all customers and store both old and new assessments.
Don't: Assign risk tiers manually or based on a single factor (e.g., "all corporate customers are high risk"). Single-factor models fail the FATF risk-based approach test.