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 do not benefit from ad-hoc queries. They benefit from predictable, well-documented endpoints.
Idempotency: The Non-Negotiable
In payments, the worst thing that can happen is not a failed transaction. It is 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 is not optional in payment systems. It is 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 should not 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 does not know they have 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 cannot 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. Do not bolt on regulatory compliance later. Design with it from day one.
- Idempotency is not 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 is not a feature. It is the product. Everything else is a nice-to-have.