BankID
A KYC identity-verification SDK embedded by five financial institutions: 16+ modular flows behind one stable public API, six million authentications, roughly fifty thousand people a day.
- Role
- iOS Engineer & Backend Developer
- Platform
- iOS SDK, distributed via SPM
- Stack
- Swift · XCFramework · Go (Echo, GORM, PostgreSQL)
- Scale
- 5 institutions · 6M+ authentications · ~50K daily users
One SDK, five banks, one identity.
BankID is the mobile identity layer behind a national digital-identity platform. It ships as a Swift SDK: 16+ independently modular, self-contained flows (authentication, OTP, liveness detection, document capture, deep linking) behind one stable public API. Five separate financial institutions embed it directly into their own apps, each under its own brand, each subject to its own security review.
I own the iOS side of the platform end to end: the flow architecture, the on-device security design, a custom video-encoding framework for liveness capture, and release engineering. That ownership now extends past iOS into the Go backend services the SDK talks to.
The problem
Five banks needed the same capability: verify a new customer's identity against a government ID and a live face, then authenticate that customer on every return visit. None of them wanted to build it five separate times, and none of them would accept a rival's engineering team anywhere near their release process.
The real requirement wasn't a feature, it was a platform: modular enough that a bank could adopt only the flows it needed, themeable enough to disappear into five different brands and languages, and stable enough that an SDK update would never be the reason a partner missed a release train.
Constraints
- Five partner banks, five compliance reviews. Payload encryption, code obfuscation, and on-device biometric handling each had to clear an independent security audit — five times, not once.
- Android parity was contractual, not aspirational. Every flow shipped on iOS needed a functioning Android equivalent before a partner would ship it.
- Partner-brand theming and RTL localization, as configuration. The SDK renders inside apps whose brand and language it doesn't control, so theming had to be built in from the start, not bolted onto a finished flow.
- Liveness detection on real hardware, on-device. Video capture and encoding for liveness had to run on years-old, low-end phones, without leaning on a cloud round-trip per frame.
- A public API stable across 16+ independently shipping modules. Distribution via Swift Package Manager meant a change to one flow could never force every partner to re-integrate.
Sixteen flows, one facade.
The system is organized around independence: each identity flow ships and versions on its own, host apps see one stable facade, and the Go backend owns everything past the device boundary.
Onboarding runs through the SDK's own flows in sequence: document capture reads a government ID, and liveness detection records and encodes a short verification video entirely on-device using the VideoCoding XCFramework. The security layer encrypts both payloads before they leave the phone. The Go backend's identity-verification service validates the submission, persists the result in PostgreSQL through GORM, and returns a verified status the SDK's authentication and OTP flows rely on for every session after that.
Each bank only ever talks to the public API. Brand, language, and RTL layout resolve from configuration at initialization, not from a forked copy of the SDK.
Decisions shaped by five customers.
Sixteen flows, not one monolith
The identity platform is decomposed into 16+ self-contained flow modules — authentication, OTP, liveness, document capture, deep linking — each independently versionable behind a single public API. A bank adopts only what it needs; a change to liveness detection can't force a re-qualification of OTP.
A custom VideoCoding XCFramework, not a vendor SDK
Liveness capture gets its own dedicated framework for on-device video encoding, built rather than licensed. At six million-plus sessions, owning the encode pipeline meant no per-verification vendor cost and full control over the quality/performance trade-off on real hardware.
Swift Package Manager as the only distribution channel
Five banks, five build systems. SPM gave predictable versioning and dependency resolution without maintaining custom integration packages per partner.
One binary, five brands
Partner theming and RTL localization resolve from configuration at initialization. The identical compiled SDK runs inside every partner's app. The alternative, a fork per bank, is how platforms stop being platforms.
Ownership extended into the Go backend
Rather than treat the backend as someone else's black box, I built and own the Go services (Echo, GORM, PostgreSQL) the SDK's flows depend on. The identity-verification contract has the same architect on both sides of the wire.
What it cost, on purpose.
Every architecture is a purchase. These are the prices that came with serving five competing customers off one codebase.
Modular flows over a monolith
Partner banks integrate incrementally and update on their own schedule. Nobody waits on a release they didn't ask for.
16+ modules each need their own backward-compatibility discipline. That's more surface area to keep stable than a single monolithic release would ever require.
A custom video-encoding framework over a vendor SDK
No per-verification licensing cost at scale, and an encode pipeline tuned exactly to the liveness-detection use case.
The team owns codec-level bugs and device-fragmentation testing that a vendor SDK would otherwise absorb.
One binary, five brands
A single artifact to build, test, and get through security review — not five diverging codebases.
Theming has to stay genuinely generic. A brand-specific request that doesn't fit the configuration model gets pushed back on, not special-cased in code.
The parts that fought back.
Five audits of the same code
Each partner bank's compliance team reviewed the identical SDK independently — payload encryption, obfuscation, and biometric handling had to be explainable and defensible five separate times, not once. The fix was treating the security model itself as a reusable artifact: one architecture write-up, one threat model, one encryption spec, handed to each new reviewer instead of rebuilt from scratch.
Liveness capture on hardware you don't choose
Recording and encoding a verification video has to hold up on a three-year-old low-end phone, not just the device it was tested on. Owning the encode pipeline in a dedicated XCFramework, instead of trusting a general-purpose vendor SDK's assumptions, is what made that tolerable.
RTL, sixteen times over
Right-to-left layout isn't one screen to get right. It's every flow, independently, staying correct as each one ships changes on its own schedule, with no shared release train forcing simultaneous QA.
Matching a platform you don't control
Full feature parity with the Android SDK, built by a separate team, meant every iOS capability needed a working Android equivalent before a partner would ship it. Two idiomatic architectures, one contractual guarantee.
The numbers behind five banks.
Banking-grade, reviewed five times.
The security model had to satisfy five independent compliance teams, not one internal review. The abbreviated map:
- Interception of identity payloads — answered by payload encryption applied independently of transport-level TLS, so a compromised transport layer alone isn't enough.
- Reverse engineering the SDK binary — answered by code obfuscation applied across the shipped artifact.
- Biometric data exposure — answered by handling biometric capture and liveness data on-device, aligned to each partner bank's own compliance and security review rather than one generic policy.
- Cross-partner trust — answered by keeping each bank's integration and audit independent; nothing in the SDK gives one partner visibility into another's traffic or data.
Because this is a live identity platform behind real banking credentials, the deeper implementation details are intentionally not published here: key handling specifics, encryption parameters, the full threat model.
From one flow to a platform.
Joined as iOS engineer & backend developer
Started architecting the SDK's modular flow structure and the public API partner banks would build against.
First flows, first distribution model
Shipped authentication and OTP as the first flow modules, and set up Swift Package Manager as the sole distribution channel.
Liveness capture gets its own framework
Built the VideoCoding XCFramework for on-device liveness video capture and encoding, and added document-capture and deep-linking flows.
Parity, theming, localization
Delivered full feature parity with the Android SDK and stood up versioning, partner-brand theming, and RTL localization as first-class release infrastructure.
Five institutions, six million sessions
The SDK reached all five partner banks and crossed six million authentication sessions, with roughly fifty thousand people using it daily.
Ownership extends into Go
Took on the Go backend (Echo, GORM, PostgreSQL): the REST APIs and identity-verification integrations the SDK's flows depend on.
Mentoring and partner integration
Leading code review, mentoring iOS engineers on the platform, and coordinating directly with partner-bank teams as new institutions integrate.
What I'd tell myself at the start.
- Design the public API for partners you'll never meet. With five separate engineering teams integrating the same SDK, every implicit assumption about threading, state, or timing becomes someone else's bug.
- Compliance review is a design input, not a final gate. Treating encryption, obfuscation, and biometric handling as things five independent auditors would each interrogate produced a more defensible design than optimizing for a single internal review.
- Parity with a platform you don't control is a scheduling constraint as much as a technical one. Plan releases around it early, rather than discovering the dependency late.
Infrastructure five banks build on.
BankID's SDK runs today inside five partner banks' apps, past six million authentication sessions and used by roughly fifty thousand people daily. Sixteen-plus modular flows ship independently behind one stable public API, with full feature parity maintained against the Android SDK built by a separate platform team.
The role has grown past iOS: I now own the Go backend those flows depend on, mentor the engineers extending the platform, and coordinate integration directly with each partner bank's team.