Skip to main content
All articles Engineering

API Latency Benchmarks for Transaction Enrichment at Neobank Scale

Daniel Okonkwo · 9 min read
API latency benchmarks for transaction enrichment

Latency benchmarks for transaction enrichment APIs are tricky to interpret because the published numbers almost never reflect the conditions under which you will actually run them. Vendors publish p50 latency in a sandbox with a single connection and call it a headline stat. Real neobank workloads are bursty, concurrent, and involve batch backfills that can spike to tens of thousands of enrichment requests per minute on integration day.

We ran our own internal throughput tests across three load profiles that correspond to real neobank deployment patterns. This post is the honest write-up: methodology, numbers, where the pipeline held, and where we found the ceiling.

All figures below come from our internal test environment. We are not citing third-party benchmarks. If you want to run your own throughput tests against the API, the Growth and Scale tier sandbox environments support load testing by prior arrangement.

The Three Load Profiles

We tested three scenarios that match the common neobank deployment lifecycle:

Profile 1: Real-time single-transaction enrichment at 5,000 tx/min. This corresponds to a neobank with roughly 80,000-120,000 active monthly users, enriching transactions as they post. Each POST /enrich request carries one transaction. Requests are evenly distributed with a small random jitter to avoid synchronized bursts from a single client.

Profile 2: Batch webhook enrichment at 50,000 tx/min. This corresponds to a neobank backfilling 90 days of transaction history on initial open-banking connection, or a scheduled overnight batch run against a day's worth of new transactions. Requests use the batch endpoint with 100 transactions per payload.

Profile 3: Spike enrichment at 500,000 tx/min. This is a stress test, not a steady-state scenario. It corresponds to a large neobank running an initial full-history enrichment across their entire user base, or a large batch import after a data migration. We test this to understand where the pipeline degrades and how gracefully.

Single-Transaction Enrichment Results (5,000 tx/min)

At this load profile, the API behaves as expected for a real-time enrichment use case. Results from our internal tests:

Percentile Latency (ms)
p50 34ms
p75 51ms
p95 78ms
p99 112ms
p99.9 198ms

At p95, latency sits at 78ms. This is consistent with the product spec that we advertise as "95th percentile under 80ms." The p99 tail at 112ms is acceptable for a real-time use case where the enrichment happens asynchronously after transaction posting. The p99.9 tail at 198ms mostly corresponds to transactions that require fallback processing paths: merchants that need a second classification pass, or requests where the string normalization layer returns an ambiguous result and we kick off a secondary lookup.

One important note: these numbers reflect the enrichment step only, not network round-trip from client to our edge. From Tampa to US-East AWS endpoints, we observe typical network additions of 8-15ms. From US-West, add 20-35ms. International clients should plan for additional round-trip time.

Batch Enrichment Results (50,000 tx/min)

Batch mode changes the performance profile substantially. When you send 100 transactions in a single payload, the per-transaction processing overhead drops significantly because the string normalization layer can be vectorized across the batch, and the model inference step runs on a tensor rather than a single input.

Metric Value
Mean batch request latency 420ms
Per-transaction cost within batch ~4.2ms
Throughput sustained 50,000 tx/min (100-tx batch at ~8 req/sec)
Error rate at target throughput 0.03%

The 420ms batch request time looks slow compared to the single-transaction p95, but the per-transaction cost of 4.2ms is 18x better than single-transaction mode for the same throughput. For backfill scenarios, this is the right tradeoff. You do not need sub-100ms response on a historical batch job. You need high throughput and low error rate.

The 0.03% error rate at 50K tx/min represents transactions that hit a retry after a transient timeout. Our client libraries implement exponential backoff by default, so these errors surface as slightly elevated latency on the affected batches, not as data gaps. Retried batches succeed on the first retry in over 99% of cases.

Stress Test Results (500,000 tx/min)

At 500K tx/min, we hit the ceiling of the current infrastructure configuration. The honest numbers:

Metric Value at 500K tx/min
Batch request latency p50 820ms
Batch request latency p95 2,400ms
Error rate (transient, retryable) 1.2%
Throughput before queue backup ~380,000 tx/min

At this volume, the queue begins to back up around the 380K mark and latency increases non-linearly above that threshold. This is expected behavior. A neobank running a one-time full-history enrichment at this scale should use our async job endpoint rather than synchronous batch requests. The async job endpoint accepts a full dataset, processes it in a distributed worker pool, and delivers results via webhook when complete. Async throughput for full-history jobs scales to several million transactions per hour with no client-side rate concern.

What These Numbers Mean for Integration Planning

The practical guidance that comes out of these tests:

For real-time enrichment on new transactions, the single-transaction endpoint is appropriate up to roughly 8,000 tx/min before you should consider batching. Above that, batch requests of 50-200 transactions reduce per-transaction latency and make your throughput more predictable.

For historical backfill on initial connection, always use batch mode with 100-200 transactions per payload. Do not loop a single-transaction endpoint at high concurrency for backfill jobs. The per-request overhead adds up fast and you will consume your rate limit allowance without proportional throughput gain.

For large initial data migrations (millions of historical transactions), use the async bulk enrichment job endpoint. Submit the full dataset as a signed S3 object reference or inline if under 10MB, receive a job ID, and collect results via webhook. This path has no real-time latency constraint and is architected for bulk processing.

The Tradeoffs We Made (and Did Not Make)

We want to be transparent about what we optimized for. Our single-transaction latency numbers are good but not the lowest in the market. A simpler lookup-based enrichment system can achieve p95 latency below 20ms because it is doing less work: matching against a fixed dictionary, not running a classification pipeline with multiple signal layers.

The tradeoff we made: lower latency or higher classification accuracy for the long tail of merchants that a lookup table cannot cover. We built for accuracy. The 78ms p95 reflects a more computationally expensive classification pipeline that handles ambiguous merchants, low-confidence cases, and multi-signal combination. If you are building a use case where 20ms enrichment latency is a hard requirement and 40% merchant coverage is acceptable, our pipeline is not the right fit.

We are not saying lower latency systems are worse. We are saying latency and coverage are in tension for transaction enrichment, and the right number depends on what you are building. For PFM and cashflow insight features, a 78ms p95 with broad merchant coverage produces better user outcomes than a 20ms p95 with frequent "Unknown Merchant" fallbacks.

Rate limits for each tier are documented on the pricing page. If your load profile exceeds Growth tier limits during a backfill, contact us before the migration: we routinely provision temporary rate limit overrides for planned data migrations at no additional charge, because a backfill that times out mid-run creates more support work for everyone.

Back to Blog
Ready to integrate? Get your free API key