ideas.
August 06, 2026 3 min read apib2bfinanceautomation

Idempotency key gateway for payment APIs

A lightweight proxy that auto-generates and enforces idempotency keys for outbound API calls, so retries never double-charge or double-ship.

The idea

A self-hosted HTTP proxy that sits between a backend and outbound APIs like Stripe, a shipping carrier, or an internal order service. It intercepts POST/PUT requests, computes a stable idempotency key from the request body plus a caller-supplied dedupe scope, attaches it as a header, and caches the first response so retries — from client timeouts, load balancer retries, or queue redelivery — return the cached result instead of re-executing the mutating call.

Why build this

Payment and order APIs are the classic double-charge failure mode: a network blip mid-request leaves the client unsure whether the call succeeded, so it retries, and without idempotency the second call creates a second charge or a second shipment. Stripe, Adyen, and most mature payment APIs support idempotency keys natively, but plenty of teams either forget to generate them correctly — reusing a key across genuinely different requests, or minting a fresh key on every retry, which defeats the point — or they call internal services that have no idempotency support at all. A drop-in proxy fixes this without touching business logic, and it's a natural fit for teams running queue-based retry systems (SQS, Sidekiq, BullMQ) where at-least-once delivery is the default and exactly-once is the team's problem to solve.

Stack sketch

  • Proxy: Go, built on httputil.ReverseProxy for low-latency passthrough
  • Key derivation: hash of method + path + normalized body + a caller-provided Idempotency-Scope header, so callers control what counts as "the same request"
  • Response cache: Redis with a configurable TTL (default 24h, matching Stripe's own window) storing status code, headers, and body
  • Concurrency guard: a short-lived lock per key (Redis SET NX) so two in-flight retries for the same key don't both hit the upstream API
  • Config: YAML per upstream target — base URL, which methods to intercept, TTL override, headers to strip before hashing
  • Observability: structured logs plus a Prometheus counter for cache hits (duplicate calls caught) vs. passthroughs

Scope for v1

  • Single upstream target per proxy instance, configured via YAML
  • Automatic key derivation from method + path + body hash — no manual key management required by callers
  • Redis-backed response cache with TTL
  • In-flight lock to prevent concurrent duplicate execution
  • A /stats endpoint showing hit/miss counts
  • Out: multi-tenant routing to many upstreams, a UI/dashboard, request/response transformation, support for non-JSON bodies

Where it could go

The next step is multi-target routing — one proxy instance in front of Stripe, a shipping carrier, and an internal order service, each with its own cache and TTL policy, so a team runs a single sidecar instead of one per integration. After that, a small dashboard showing recent duplicate calls caught, with the potential double-charge avoided, turns the tool from invisible infrastructure into something a finance or ops team can point to as evidence it's paying for itself. Longer term, extending the same pattern to GraphQL mutations and gRPC unary calls would cover teams that have already moved off plain REST.

Watch out for

Hashing the request body for key derivation breaks if two logically-identical requests serialize differently — key ordering, floating-point formatting, timestamps embedded in the payload. The proxy needs a canonicalization step, and any field that legitimately changes between "retries" of the same logical operation has to be excluded from the hash or the whole scheme silently stops deduplicating.