Use case

Recurring crypto billing for subscriptions

Card subscriptions renew themselves: the processor charges a stored credential every month and you react to the result. Crypto has nothing to store and nothing to charge — a customer signs one transaction at a time, from a wallet you do not control. Recurring crypto billing is the honest version of that loop. Your billing system issues a fresh, rate-locked invoice each cycle, the customer pays from their wallet, and a signed webhook tells you the moment to extend access. There is no auto-charge to fake; there is a clean event you react to.

01

There is no card to charge, so each cycle is a new invoice

A card subscription hides the payment from the merchant. The customer enters a credential once, and on every renewal date the processor pulls the fee in the background. Your billing system only ever sees the outcome — a success, a decline, a dunning trigger — and never the act of paying.

Crypto removes the stored credential entirely. There is no token you can charge unattended; a wallet signs a transaction at the moment its owner decides to, for the exact amount shown, on whichever chain they hold funds. You cannot reach into that wallet next month and pull the next period's fee. Honest recurring billing means accepting that and building the loop around it: when a cycle comes due, you issue a new invoice, the customer chooses to pay it, and you act on the confirmation.

This is not a workaround or a lesser version of a card subscription — it is the correct shape for the rail. The customer keeps custody until they sign, the price is re-stated in crypto at the rate that holds right then, and every period is its own observable payment with its own state. The work moves from "react to a charge that happened" to "issue an invoice and react to whether it was paid."

  • No saved credential means no unattended renewal — every cycle is a customer-initiated payment.
  • Recurring billing in halfin is invoice-per-cycle, not a token you re-charge.
  • The fee is re-quoted in crypto each period at the live rate, then locked.
  • Access follows a confirmed payment, not a charge attempt — the signal is the webhook.
02

The cycle loop: create, lock, confirm, extend

A renewal cycle is short to describe because it reuses one primitive. When a period comes due, your billing job creates a fiat-anchored invoice for what the customer owes — say USD 49. halfin quotes the equivalent in the assets you accept and locks that rate at activation, so the customer pays a fixed figure inside a defined window instead of chasing a number that drifts while they read it.

The customer settles through hosted checkout or a checkout you render yourself against the same API. halfin watches the chain, applies that chain's confirmation threshold, and credits the deposit reorg-aware. When the invoice resolves, your server receives an HMAC-signed webhook. Verify the signature first, then extend the subscription period and schedule the next cycle — that webhook, not the browser redirect and not a polling loop, is the authoritative signal that the period was paid.

Because the amount is fiat-anchored, your pricing page stays in dollars or euros and your revenue reporting stays in fiat, while the customer settles in USDT, USDC, or a native asset. Underpaid and overpaid cycles land in defined states rather than silent mismatches: a customer who sends slightly too little does not quietly get a full month of access. The whole loop maps onto the deposit, confirm, credit, reconcile sequence your finance team already thinks in.

Cycle stageWhat halfin doesWhat your billing system does
Period comes dueCreate a fiat-anchored invoice with an idempotency key scoped to the cycle
Invoice activatesQuote the asset amount and lock the rate; stamp an expiryPresent hosted or self-hosted checkout to the customer
Customer paysWatch the chain, apply the per-chain confirmation threshold, credit reorg-awareWait — do not extend access on the redirect alone
invoice.paid webhookSend the HMAC-signed eventVerify the signature, extend the period, schedule the next cycle
invoice.expired webhookSend the HMAC-signed event after the window lapsesHand off to dunning: reminder, grace period, or downgrade
03

Idempotency keeps a retried billing job from double-billing

A scheduler that bills subscriptions will, sooner or later, run twice for the same cycle — a retry after a timeout, a crashed worker that restarts, a duplicate cron tick. Without protection, that means two invoices for one period and a confused customer who sees a second amount to pay.

The invoice create call carries an idempotency key, and that key is what makes the job safe to retry. Scope the key to the cycle — an account ID plus the billing period is a natural shape — and a repeat call with the same key returns the existing invoice instead of issuing a second one. Your billing job does not need to track "did I already create this"; the key does it for you.

There is no separate recurring endpoint to learn. The same call is what a cron job, a usage-rollup worker, or a self-serve upgrade button makes — the invoicing primitive called once per period, with a cycle-scoped idempotency key so a retried run never bills twice.

# Called once per billing cycle by your scheduler.
# idempotency_key is scoped to the cycle (account + period), so a
# retried job returns the existing invoice instead of billing twice.
curl -X POST https://api.thehalfin.com/api/v1/invoices \
  -H "X-API-Key: $HALFIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_fiat": "49.00",
    "fiat_currency": "USD",
    "deferred": true,
    "description": "Pro plan — acct_42 — 2026-06",
    "idempotency_key": "sub-acct_42-2026-06"
  }'

# halfin locks the rate at activation, pins the payable asset amount,
# and returns an invoice you present via hosted or self-hosted checkout.
# Track the cycle through signed webhook events — verify the HMAC
# signature before extending access. Schemas: docs.thehalfin.com.
04

The webhook is the only thing that should grant access

It is tempting to extend the subscription when the customer lands back on your success page, but the redirect is not proof of payment — a customer can close the tab before it fires, and a request to that URL can be forged. The authoritative signal is the signed webhook your backend receives when the invoice resolves on-chain.

halfin signs each event with an HMAC signature. Verify that signature before you act on the payload — that verification is the line between extending a subscription correctly and extending it on a forged request. Only after the signature checks out should you read the event, extend the period, and set the next renewal date.

The canonical events you build the loop around are invoice.confirming (the cycle's invoice is live and awaiting payment), invoice.paid (the period settled — extend access), invoice.underpaid and invoice.overpaid (the amount received did not match — apply your shortfall or excess policy rather than granting a full period blindly), and invoice.expired (the window lapsed without sufficient payment — treat it as a non-renewal). Each is a clean, separate event your billing logic can act on; the exact payload schema lives in the docs.

  • invoice.confirming — the cycle's invoice is live, rate locked, awaiting payment.
  • invoice.paid — the period settled; verify the signature, then extend access.
  • invoice.underpaid / invoice.overpaid — amount mismatch; apply your own policy.
  • invoice.expired — the window lapsed; treat it as a non-renewal for dunning.
  • Verify the HMAC signature on every event before taking any business action.
05

A missed cycle is an expiry, not a decline — dunning changes shape

On cards, a failed renewal is a decline: the processor tried to charge and the bank said no, and your dunning flow retries the same credential on a schedule. There is nothing to retry in crypto, because nothing was charged. A customer who does not pay a cycle simply lets the invoice run out its window, and you receive an invoice.expired event.

That maps cleanly onto retention logic you almost certainly already have. Treat the expiry as a non-renewal and hand it to your existing dunning flow — a reminder email, a grace period during which access continues, a downgrade to a free tier, or whatever your product does when a card fails, re-pointed at an expiry event instead of a decline code. The trigger changes; the playbook does not.

Because each cycle is its own invoice, a missed period is unambiguous. There is no partial-charge limbo and no retry queue silently draining a stale credential — one expired invoice is one non-renewal you can act on, and a customer who decides to pay later does so against a fresh invoice quoted at the current rate.

06

Where this leads next

Recurring crypto billing is the invoicing primitive called once per cycle with a rate lock, so the cleanest place to start is invoicing itself — it owns the fiat anchor, the activation-time rate lock, the expiry window, and the underpaid and overpaid states this whole loop relies on. Read that first, because everything here is that primitive in a scheduler.

From there, the integration is an API-first job: programmatic invoice creation per cycle, an idempotency key scoped to the period, and a signed-webhook handler that extends access. Wire the webhook before anything else that grants entitlement, since it is the only authoritative signal a cycle was paid. The parent SaaS use case covers the surrounding surface — usage-based billing, checkout choice, and paying a developer ecosystem from the same balance.