Skip to main content

What is a keeper?

A keeper is any script that calls charge() on the Vowena contract for subscriptions that are due. charge() is permissionless — the contract validates every precondition on-chain, so the keeper just signs an invoke op and submits it. The keeper pays the tiny transaction fee (~$0.00001 in XLM) while USDC flows directly from subscriber to merchant.
You don’t have to build this yourself. The Vowena dashboard ships a managed keeper that runs as a Vercel cron every minute. This page is for teams that want to self-host.

How it works

  1. Keeper enumerates every subscription that might be due (discovery).
  2. For each subscription, it builds a charge() tx via client.buildCharge.
  3. It signs with its own keypair and submits to the network.
  4. If the charge is due and the allowance is sufficient, USDC moves subscriber → merchant and charge_ok is emitted. If not, the contract rejects harmlessly — no funds move, nothing breaks.
The keeper never holds USDC. It only holds a small XLM balance for tx fees.

A minimal keeper loop

The only two non-obvious bits are (a) how to discover subs, and (b) why submissions must be serial.
Don’t parallelize submissions from one signer. Stellar requires sequence numbers to be the exact next value per source account. Firing charge() transactions in parallel with the same keeper means every tx after the first lands with tx_bad_seq. Serial + fresh server.getAccount() per tx is the safe pattern.

Scheduling

A cron or setInterval call every 60 seconds is plenty for most plans. charge() is idempotent within a period — calling it before the next period is due simply no-ops, so you can’t over-bill by polling too often.

Using the dashboard’s managed keeper

If you don’t want to operate your own infra, enable the dashboard’s managed keeper instead:
  • Vercel cron hits /api/cron every minute.
  • The cron signs with VOWENA_ISSUER_SECRET (set once in your Vercel env).
  • Submissions are serial with a fresh account fetch per tx — same pattern as above.
See Keeper setup for step-by-step configuration.

Failure modes

All of these are handled automatically by calling charge() — the keeper doesn’t need to inspect subscription state first.