StrimzStrimz
Self-hosting

Self-hosting the scheduler

Cron jobs and the webhook delivery queue.

The scheduler is a Nest, Fastify, and BullMQ app. It owns the cron jobs (subscription sweeper, session expiry, invoice-overdue marker, subscription-lapsed transitions, plan upgrades, fee-drift checks, contract alerts, gas balance and notification emails) and the webhook delivery queue. At boot it also warms the webhook-secret cache the delivery worker reads from.

The scheduler signs its own transactions with its own key, separate from the API relayer's key. Set SCHEDULER_SIGNER_PROVIDER to aws-kms (required in production) or software with SCHEDULER_PRIVATE_KEY for local work. The scheduler refuses to boot if its signing address equals RELAYER_ADDRESS.

Build

cd apps/scheduler
pnpm build
pnpm start:prod

Configuration

Required:

NODE_ENV=production
DATABASE_URL=postgresql://...
REDIS_URL=redis://...

ARC_ENVIRONMENT=mainnet
ARC_RPC_URL=https://your-arc-rpc.com

SUBSCRIPTIONS_ADDRESS=0x...
AGENT_ESCROW_ADDRESS=0x...
REGISTRY_ADDRESS=0x...
RELAYER_ADDRESS=0x...

SCHEDULER_SIGNER_PROVIDER=aws-kms
SCHEDULER_KMS_KEY_ID=...
SCHEDULER_SIGNER_ADDRESS=0x...
AWS_REGION=us-east-1

STRIMZ_WEBHOOK_SIGNING_SECRET=...
WEBHOOK_SECRET_ENCRYPTION_KEY=...
STRIMZ_ADMIN_ALERT_EMAIL=ops@your-domain.com

STRIMZ_WEBHOOK_SIGNING_SECRET is at least 32 characters. WEBHOOK_SECRET_ENCRYPTION_KEY is 64 hex characters and must match the API's value exactly. ARC_ENVIRONMENT is required when NODE_ENV=production.

Optional, with their defaults:

PORT=4200
ARC_USDC_ADDRESS=0x3600000000000000000000000000000000000000
WEBHOOK_DELIVERY_TIMEOUT_MS=10000
WEBHOOK_MAX_ATTEMPTS=5

SUBSCRIPTION_SWEEPER_CRON=0 */15 * * * *
INVOICE_OVERDUE_CRON=0 0 * * * *
PLAN_UPGRADE_CRON=0 */5 * * * *
CONTRACT_ALERT_CRON=0 * * * * *
FEE_DRIFT_CRON=0 */5 * * * *
GAS_BALANCE_CRON=0 */15 * * * *
MERCHANT_NOTIFICATION_CRON=*/30 * * * * *

RESEND_API_KEY=...
RESEND_FROM_EMAIL=Strimz <noreply@mail.strimz.finance>
RESEND_REPLY_TO=support@strimz.finance

On-chain calls

The scheduler signs these itself:

OperationContract / Function
Charge a subscriptionStrimzSubscriptions.batchCharge(ids[], attemptIds[]), one per call
Cancel a subscriptionStrimzSubscriptions.cancel(subId)
Set a merchant's feeStrimzRegistry.setFeeBps(...) (plan upgrades)
Agent escrow jobsStrimzAgentEscrow create, release, dispute, cancel (not live yet)

The subscription cancel needs the scheduler to hold CHARGER_ROLE on a subscriptions contract from the next testnet redeploy (#142). On the current deployment it reverts.

Multi-instance safety

Two scheduler instances behind a load balancer are safe to run side by side:

  • The subscription sweeper uses Postgres UPDATE … WHERE chargeLock=false to claim work atomically.
  • BullMQ guarantees each job is delivered to exactly one worker.
  • The webhook delivery worker no-ops on any WebhookDelivery.status that's already terminal.

If you're processing over 100k webhook deliveries a day, scale horizontally. Throughput grows roughly linearly with replicas.

Observability

  • /healthz and /readyz
  • Ops alerts (low gas balance, contract admin events) go to STRIMZ_ADMIN_ALERT_EMAIL

Failure modes

  • DB outage. Workers fail their current job. BullMQ retries. The job stays in the queue.
  • Redis outage. The scheduler can't pick up new jobs. Existing in-flight work completes.
  • The scheduler broadcasts but crashes before recording. The on-chain side succeeded. The indexer projects the resulting event, and the contract's charge-attempt id stops the same charge landing twice.

Source

github.com/StrimzLab/strimz/tree/main/apps/scheduler

On this page