A creator subscription is an invoice per cycle, not a saved card
On platforms built around cards, a fan subscribing to a creator enters a credential once and the processor charges it every renewal date. The creator's premium tier — the members-only feed, the badge, the gated stream — stays on as long as those background charges succeed. The fan never signs anything again; the platform never sees the act of paying, only the outcome.
Crypto has nothing to store and nothing to pull. A fan's 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 take the next period's subscription fee. So the honest shape of a creator subscription is invoice-per-cycle: when a period comes due, you issue a new invoice, the fan chooses to pay it, and the fan's entitlement follows the confirmed payment.
This is the correct model for the rail, not a downgrade from card billing. The fan keeps custody until they sign, the fee 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 platform's job moves from "react to a charge that ran" to "issue the cycle's invoice and react to whether the fan paid it." Any product promising to silently auto-renew a self-custodied wallet is not describing how these chains work.
- No saved credential means no unattended renewal — every cycle is a fan-initiated payment.
- A creator subscription 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 at activation.
- Entitlement follows a confirmed payment, not a charge attempt — the signal is the webhook.
The subscription loop: create, lock, pay, grant
A renewal cycle is short to describe because it reuses one primitive. When a fan's period comes due, your backend creates a fiat-anchored invoice for what the subscription costs — say USD 5 a month for a creator's premium tier. halfin quotes the equivalent in the assets you accept and locks that rate at activation, so the fan pays a fixed figure inside a defined window instead of watching the number drift while they open their wallet.
The fan settles on hosted checkout — a page halfin renders at checkout.thehalfin.com that owns the QR, the deposit address, the network choice, and the live status from waiting through confirmed. You write no wallet-connect code for the subscribe button. 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.
The fan's access turns on off that webhook, not off the redirect. A fan can pay and close the tab before the success page loads; the signed invoice.paid event still arrives, and that is what should unlock the premium tier and set the next renewal date. Because the amount is fiat-anchored, the creator's price stays in dollars and your revenue reporting stays in fiat, while the fan settles in USDT, USDC, or a native asset. A short shortfall lands in an underpaid state rather than quietly granting a full month — the loop maps onto the deposit, confirm, credit, reconcile sequence your finance team already thinks in.
| Cycle stage | What halfin does | What your platform does |
|---|---|---|
| Period comes due | — | Create a fiat-anchored invoice with an idempotency key scoped to the cycle. |
| Invoice activates | Quote the asset amount, lock the rate, stamp an expiry. | Send the fan to the hosted checkout URL in the response. |
| Fan pays | Watch the chain, apply the per-chain confirmation threshold, credit reorg-aware. | Wait — do not grant access on the redirect alone. |
| invoice.paid webhook | Send the HMAC-signed event. | Verify the signature, grant the entitlement, schedule the next cycle. |
| invoice.expired webhook | Send the HMAC-signed event after the window lapses. | Hand to dunning: reminder, grace period, or downgrade. |
Entitlement is granted off the signed invoice.paid webhook
The single decision this whole use case turns on is when to flip a fan's premium tier from off to on. It is tempting to do it when the fan lands back on your success page, but the redirect is not proof of payment — a fan 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 granting access correctly and granting it on a forged request. Only after the signature checks out should you read the event, unlock the creator's gated content for that fan, and record 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 — grant 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 entitlement 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 grant the entitlement.
- invoice.underpaid / invoice.overpaid — amount mismatch; apply your own policy, do not unlock blindly.
- 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.
One create call per cycle, keyed so a retry never double-bills
The integration is the invoicing primitive called once per period. Your scheduler bills a fan's subscription when the period comes due; you create a fiat-anchored invoice and send the fan to the hosted checkout URL in the response. Pass an idempotency key scoped to the cycle — a subscription ID plus the billing period is a natural shape — and a retried job that reuses the same key returns the existing invoice instead of issuing a second one. A crashed worker, a duplicate cron tick, or a timeout-retry never bills the fan twice.
There is no separate recurring or subscription endpoint to learn, and no batch invoice call. The same single create is what a monthly cron job, a self-serve resubscribe button, or a gifted-membership flow makes — the invoicing primitive once per period, with a cycle-scoped idempotency key for safety. Read each invoice back, and the cycle's state is the invoice's own, not a guess assembled from logs.
# Called once per subscription cycle by your scheduler.
# idempotency_key is scoped to the cycle (subscription + 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": "5.00",
"fiat_currency": "USD",
"deferred": true,
"description": "Premium tier — creator_8841 — fan_42 — 2026-06",
"idempotency_key": "sub-creator_8841-fan_42-2026-06"
}'
# halfin locks the rate at activation, pins the payable asset amount,
# and returns an invoice with a hosted checkout URL you send the fan to.
# Grant access only on the HMAC-signed invoice.paid webhook — verify
# the signature first. See docs.thehalfin.com for the full response schema.A lapsed subscription is an expiry, not a decline
On cards, a failed renewal is a decline: the processor tried to charge a stored credential and the bank said no, and your dunning flow retries that same credential on a schedule. There is nothing to retry in crypto, because nothing was charged. A fan who does not renew a cycle simply lets the invoice run out its window, and you receive an invoice.expired event.
That maps cleanly onto retention logic a creator platform almost certainly already has. Treat the expiry as a non-renewal and hand it to your existing flow — a reminder that the subscription is about to lapse, a grace period during which the premium tier stays on, a downgrade to the free follower view, or a win-back offer. The trigger changes from a decline code to an expiry event; 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 fan who decides to come back later resubscribes against a fresh invoice quoted at the current rate — which is also why a creator who raises their price next month simply has the next cycle's invoice reflect it, with no stored amount to migrate.
Where the income side connects to the rest
Subscription monetization is the income half of a creator platform: it is the invoicing primitive called once per cycle with a rate lock, presented on hosted checkout, with entitlement gated on the signed webhook. The cleanest place to read further 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. Hosted checkout owns the page the fan actually pays on, so you ship a subscribe button without building wallet code.
The other half of the platform is the outgoing side — paying creators their share of subscription revenue — which the parent streamers use case covers: idempotent mass payouts for the scheduled roster run, single payouts for an off-cycle correction, and balance conversion when a creator wants paying in a different asset than fans subscribed in. The same dashboard and the same signed webhooks tie the money coming in from subscriptions to the money going out to creators.