back to writing
· 8 min

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