When we set out to build HMD Payments, the premise sounded simple: a payment API with zero fees. The technical reality was anything but simple. This is a post about the architectural decisions - the ones we got right, and the ones that cost us weeks.
The Problem We Were Solving
Most payment APIs in our market charged between 2-5% per transaction. For small businesses and creators, these fees ate into margins. We wanted to build something that eliminated the middleman fee through direct integrations with payment providers: Papara, bank IBAN transfers, cryptocurrency, and card processors.
The business model was sustainable through volume-based institutional agreements rather than per-transaction fees to the merchant.
API Design: REST, Not GraphQL
We chose REST. In fintech, predictability matters more than flexibility. Every payment operation maps cleanly to a resource and an HTTP method:
POST /v1/payments- Create a payment intentGET /v1/payments/:id- Retrieve payment statusPOST /v1/refunds- Initiate a refundGET /v1/transactions- List transactions with pagination
GraphQL would have introduced unnecessary complexity for an API where the query patterns are well-known and fixed. Payment integrations don't benefit from ad-hoc queries - they benefit from predictable, well-documented endpoints.
Idempotency: The Non-Negotiable
In payments, the worst thing that can happen isn't a failed transaction - it's a duplicate transaction. A network timeout, a retried request, a client bug - any of these could cause a double charge. Idempotency keys prevent this.
Every mutating request requires an Idempotency-Key header. The server stores the key and its response. If the same key is sent again, the server returns the cached response without re-executing the operation.
This isn't optional in payment systems. It's the difference between "we can recover" and "we just charged someone twice."
Multi-Provider Architecture
The core architectural challenge was supporting multiple payment providers behind a single API surface. A merchant shouldn't need to know whether a payment is being processed via Papara, IBAN, or card - they make one API call and we route it.
We used a provider abstraction:
PaymentIntent
├── ProviderRouter (selects provider based on method + currency)
├── PaparaProvider
├── IBANProvider
├── CryptoProvider
└── CardProvider
Each provider implements a common interface: createPayment(), checkStatus(), refund(). The router selects the provider based on the payment method the merchant specifies. This made adding new providers straightforward - implement the interface, register the provider, done.
Webhook Reliability
Payment events need to reach merchants reliably. A missed webhook means a merchant doesn't know they've been paid. We built the webhook system with guaranteed delivery:
- Queue all events. Every payment state change is written to a persistent queue.
- Retry with backoff. Failed deliveries are retried with exponential backoff - 1 min, 5 min, 30 min, 2 hours, 24 hours.
- Dead letter queue. After 5 failed attempts, the event moves to a dead letter queue for manual review.
- Signature verification. Every webhook includes an HMAC signature so merchants can verify authenticity.
Security Architecture
Handling money means handling trust. Our security posture included:
- API key scoping. Each key has explicit permissions - a key that can create payments can't necessarily issue refunds.
- Request signing. Critical operations require request body signing, not just authentication.
- Rate limiting. Per-key rate limits prevent abuse. Configurable per merchant based on their plan.
- Audit logging. Every API call is logged with full request/response metadata. Immutable, append-only, retained for 7 years.
What Led to the Acquisition
HMD Payments was acquired in 2022. The technical due diligence focused heavily on three things:
- API design quality. Clean, well-documented, versioned. The acquiring company could integrate it without extensive refactoring.
- Multi-provider flexibility. Adding new payment methods required implementing one interface, not rewriting infrastructure.
- Reliability track record. Over 2 million transactions processed with zero data loss incidents.
Lessons Learned
- Start with compliance. Don't bolt on regulatory compliance later. Design with it from day one.
- Idempotency isn't optional. In any system that handles money or irreversible operations, idempotency keys are mandatory.
- Boring technology for the foundation. We used PostgreSQL for transactions (ACID matters when money is involved) and Redis for caching. No exotic tech in the critical path.
Building a payment system teaches you that reliability isn't a feature - it's the product. Everything else is a nice-to-have.
If you're building a fintech product, start with idempotency and audit logging. Everything else can be iterated on. Those two can't.