Check Pipeline
An end-to-end remote deposit capture system that processes check images through an automated pipeline and produces X9.100-187 Image Cash Letter files.
Work in progress
Overview
A remote deposit capture (RDC) system that takes a photograph of a check and processes it through an automated pipeline, ultimately producing a valid X9.100-187 Image Cash Letter file for clearing against a receiving bank.
The system demonstrates the full lifecycle of financial check processing: image capture, quality validation, MICR line recognition, amount verification, duplicate detection, cash letter assembly, and submission to a receiving institution simulator. Each stage runs as an independent worker consuming from a message queue, allowing per-stage scaling and retry semantics.
All check images are synthetically generated — no real financial data is used anywhere in the project.
Architecture
The system uses an event-driven microservice architecture with SQS queues connecting independent processing stages:
React Frontend (Vite + TypeScript)
|
v
Gin REST API (Go)
| \
v v
PostgreSQL AWS S3 (KMS-encrypted images)
^
|
SQS Queues → Stage Workers (IQA → MICR → CAR/LAR → Dupe → Cash Letter)
|
v
ICL Receiver (simulated clearing bank)
Event-driven stages. Each processing stage (image quality, MICR recognition, amount verification, etc.) runs as an independent worker process consuming from its own SQS queue. When a stage completes, it publishes to the next queue in the pipeline. This decoupling means stages can be scaled independently, retried without affecting other stages, and monitored individually via dead-letter queues.
Single worker binary. All stage workers compile into one Go binary (icl-worker) with subcommands per stage (e.g., icl-worker iqa, icl-worker micr). This simplifies the container image while still allowing Kubernetes to scale each stage independently by running different subcommands in separate pods.
LocalStack for development. The same code path targets LocalStack (local) and real AWS (production) via environment variables only. S3, SQS, and KMS all run locally in Docker Compose with zero AWS costs during development.
Processing Pipeline
Each check item progresses through a defined state machine. Every transition is recorded as an immutable, hash-chained audit entry:
captured → iqa_passed → micr_read → amount_read → dupe_checked → bundled → sent → posted | returned | rejected
| Stage | Purpose | Status |
|---|---|---|
| Image Capture | Accept front/back check images, upload to S3, create deposit record | Complete |
| Image Quality Analysis | Validate dimensions, brightness, contrast, skew, and corner content | Complete |
| MICR Recognition | Parse E-13B MICR line: routing number, account number, check number | In progress |
| Amount Recognition | CAR/LAR (courtesy and legal amount recognition) with confidence scoring | Planned |
| Duplicate Detection | Exact match and perceptual image hashing to catch resubmissions | Planned |
| Cash Letter Assembly | Bundle approved items into X9.100-187 files using moov-io/imagecashletter |
Planned |
| Receiving Simulator | Validate incoming cash letters, simulate returns with X9.100-188 codes | Planned |
Items that fail any stage are routed to an exception queue for manual review. The exception handling UI lets operators inspect failed items, view failure reasons, and manually key corrected values.
API Design
The REST API manages deposits, items, exceptions, and cash letters:
| Group | Endpoints | Purpose |
|---|---|---|
/deposits |
POST / — create deposit with imagesGET / — list depositsGET /:id — deposit details with items
|
Capture sessions. Each deposit contains one or more check items with front/back image pairs. |
/items |
GET / — list itemsGET /:id — item details with audit trail
|
Individual check items with recognized fields, images, and processing state. |
/items/:id/audit |
GET / — audit chain with hash verification
|
Returns the full hash-chained audit trail for an item, with tamper detection. |
/exceptions |
GET / — list exceptionsPOST /:id/resolve — resolve exception
|
Items that failed automated processing and need human review. |
/cashletters |
GET / — list cash letters
|
Assembled X9.100-187 files ready for submission. |
All responses follow the standard envelope: { "result": bool, "data": T, "error": string }.
Audit Trail
Every state transition for every check item is recorded in an append-only item_states table with cryptographic hash chaining:
hash = SHA256(prev_hash || item_id || state || payload || timestamp)
Each record links to the previous via its hash, forming an immutable chain. The verification endpoint walks the entire chain for an item and recomputes hashes to detect any tampering. If a single record has been modified, every subsequent hash will fail verification.
This is a critical compliance feature in financial systems — regulators require provable audit trails for every check that enters the clearing process. The hash chain provides cryptographic proof that no record has been altered after the fact.
The frontend includes an audit trail viewer that displays the chain for any item, with visual indicators for verified and broken links.
Image Quality Analysis
The IQA stage validates check images using pure Go image processing — no OpenCV or external dependencies. Five tests run against each image:
| Test | Criteria | Why It Matters |
|---|---|---|
| Dimensions | Aspect ratio 1.5–4.0, minimum 200×80 px | Rejects non-check images (selfies, screenshots) |
| Darkness | Average brightness 30–240 | Catches under/overexposed captures |
| Contrast | Standard deviation > 15 | Ensures text is distinguishable from background |
| Skew | < 5° rotation | Excessive tilt prevents reliable MICR reading |
| Corners | All four corners have content | Detects cropped or partially captured checks |
Results are stored with measured values and pass/fail thresholds. Failed items are routed to the exception queue with specific failure reasons, so operators know exactly what went wrong.
Data Model
PostgreSQL with versioned migrations managed by golang-migrate. Seven migrations define the schema:
| Table | Purpose |
|---|---|
deposits |
Capture sessions with user, item count, declared total, and status |
items |
Individual checks with front/back images, MICR fields, amounts, and processing state |
item_states |
Append-only audit chain with hash-linked state transitions |
iqa_results |
Image quality test results with measured values and pass/fail thresholds |
exceptions |
Items needing human review with failure reasons and resolution notes |
cash_letters |
Assembled X9.100-187 files with bundle/item counts and S3 keys |
accounts / returns |
Receiving institution simulator data |
Infrastructure
Local development uses Docker Compose with PostgreSQL and LocalStack (S3, SQS, KMS). No AWS account needed during development.
AWS resources are defined in Terraform:
- S3 bucket with versioning, object lock (governance mode), and 90-day Glacier lifecycle
- KMS customer-managed key for envelope encryption of check images
- SQS queues per processing stage, each with its own dead-letter queue
- IAM roles with least-privilege access policies
Production deployment targets Kubernetes (k3s) on a DigitalOcean droplet with Traefik ingress and Let's Encrypt TLS.
Tech Stack
- Language
- Go
- Web Framework
- Gin
- Database
- PostgreSQL
- Message Queue
- AWS SQS (LocalStack in dev)
- Object Storage
- AWS S3 with KMS encryption
- ICL Standard
- moov-io/imagecashletter (X9.100-187)
- Routing Validation
- moov-io/fed (ABA routing numbers)
- Migrations
- golang-migrate
- Frontend
- React 19 + TypeScript + Vite
- Linting
- Oxlint
- Infrastructure
- Docker Compose, Terraform, Kubernetes (k3s)
- Image Processing
- Pure Go (no OpenCV dependencies)
GitHub