Money-safe APIs: idempotency for crowdfunding
How Weave.Fund avoids double charges with ledger-first design.
Ledger first, balances never
On Weave.Fund, money movement is an append to a ledger, never a mutation of a balance column. Balances are derived. If two requests race, both rows exist and reconciliation has ground truth instead of a silently overwritten number.
Idempotency keys on every money endpoint
The client sends an Idempotency-Key header; the server stores the key with the request hash and the response. Retries — user double-taps, network timeouts, webhook redelivery — return the original result instead of charging twice.
@router.post("/payments/intents", status_code=201)
async def create_intent(
payload: PaymentIn,
idempotency_key: str = Header(alias="Idempotency-Key"),
db: AsyncSession = Depends(get_db),
):
existing = await ledger.find_by_key(db, idempotency_key)
if existing:
return existing.response # safe retry, no double charge
return await ledger.append(db, payload, idempotency_key)Reconcile with webhooks, not hope
Payment providers lie by omission — callbacks get lost. A scheduled reconciliation job replays unsettled intents against the provider and closes drift. I would rather page on reconciliation lag than discover missing money from a user complaint.
Building something that needs this rigor?
Send a 2-line brief — I reply with architecture thoughts, not fluff.
Hire me