Strimz
Guides

Build a subscription

Recurring revenue, on stablecoins, with one customer signature.

Strimz subscriptions are pull-based. Your customer signs one transaction that lets the contract charge them a fixed amount on a fixed schedule. After that, the Strimz scheduler does the work every period. There are no re-prompts to deal with, no card-on-file expiry, and no "your bank declined the renewal" emails.

Define a plan

const plan = await strimz.subscriptionPlans.create({
  name: 'Pro',
  amount: '20000000', // 20 USDC, 6 decimals
  currency: 'USDC',
  interval: 'monthly',
  intervalCount: 1,
  trialPeriodDays: 14,
})
 
console.log(plan.checkoutUrl)
// → https://strimz.finance/sub/plan_pro_monthly

Plans are templates. They don't represent a single customer; they represent a recurring price the customer can subscribe to.

Customer subscribes

Send the customer to plan.checkoutUrl. They connect a wallet, see "Subscribe, 20 USDC/month", and sign one message.

The message is an EIP-2612 Permit, a structured authorisation USDC supports natively that grants the StrimzSubscriptions contract a recurring allowance. Strimz's relayer takes that signed permit and calls permitAndCreateSubscription in a single atomic transaction. The permit is consumed and the subscription is created in the same block. There is no approve-then-subscribe round trip.

The full security model lives on the meta-tx flow page: what the customer signs, why the relayer cannot move funds on its own, when the signature expires.

You receive subscription.created immediately and another webhook each time a period charges (subscription.charged).

What happens at charge time

The Strimz scheduler runs a sweep every 15 minutes (configurable). It finds subscriptions whose nextChargeAt is in the past, atomically locks them via chargeLock, and enqueues a job per locked sub onto strimz.subscription.due.

The worker calls batchCharge(subscriptionIds, chargeAttemptIds) on the contract. Each entry has a chargeAttemptId = keccak256(subscriptionId, periodEndAt) so retries can't double-charge. The contract pulls USDC from the customer's wallet and emits SubscriptionCharged (or SubscriptionChargeSkipped with the reason). The indexer projects both into your DB.

Handle a failed charge

A failed charge moves the subscription to at_risk and fires subscription.charge_failed with the on-chain outcome:

outcomeWhat it means
insufficient_fundsCustomer's USDC balance is below the charge amount
revoked_approvalCustomer revoked your contract's allowance
cancelledCustomer cancelled on-chain between your charge attempts (rare race)
skippedPeriod not yet due (defensive; should never reach you)

If you've enabled the agent's recovery capability, the customer gets an email. After your configured grace window (24h / 48h / 72h), the sub flips to lapsed.

Cancel a subscription

There are two paths to cancellation.

Merchant-initiated. Say the customer's account was deleted on your side:

await strimz.subscriptions.cancel(subscriptionId, { reason: 'merchant initiated' })

This marks the DB optimistically and enqueues an on-chain cancel. The subscription.cancelled webhook fires once the on-chain tx confirms.

Customer-initiated. The customer calls the contract directly from their wallet. The indexer picks up SubscriptionCancelled and flips the row.

Either way, the customer's wallet stops getting charged. Their existing token allowance still exists. To revoke the allowance fully, the customer calls approve(spender, 0) on the USDC contract. Strimz does not auto-revoke because future plans might use the same spender.

Trial periods

trialPeriodDays: 14 on the plan means the first charge is now + 14 days instead of now. The customer signs immediately so you know they've committed. Strimz holds off on taking their money for two weeks. Standard trial behaviour.

Pricing math

Strimz charges a per-transaction fee on every charge based on your tier. For a Starter (0.5%) merchant subscribing a customer to a 20 USDC monthly plan:

  • Customer pays: 20.00 USDC
  • Strimz fee: 0.10 USDC (0.5%)
  • Merchant receives: 19.90 USDC

The fee is taken on-chain, in the same transaction, by StrimzPayments. There's no separate invoicing. Your dashboard's MRR view shows both gross and net.

On this page