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
Configuration
Required:
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:
On-chain calls
The scheduler signs these itself:
| Operation | Contract / Function |
|---|---|
| Charge a subscription | StrimzSubscriptions.batchCharge(ids[], attemptIds[]), one per call |
| Cancel a subscription | StrimzSubscriptions.cancel(subId) |
| Set a merchant's fee | StrimzRegistry.setFeeBps(...) (plan upgrades) |
| Agent escrow jobs | StrimzAgentEscrow 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=falseto claim work atomically. - BullMQ guarantees each job is delivered to exactly one worker.
- The webhook delivery worker no-ops on any
WebhookDelivery.statusthat's already terminal.
If you're processing over 100k webhook deliveries a day, scale horizontally. Throughput grows roughly linearly with replicas.
Observability
/healthzand/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.

