Webhooks

Receive signed HTTP callbacks for every on-chain Cron Protocol event — subscription activations, charges, failures, and cancellations.

Overview

The @cron/webhooks service listens to CronRegistry events on-chain and delivers HMAC-SHA256 signed HTTP POST payloads to your configured endpoint. It also optionally sends wallet-native notifications via Push Protocol.

Events

  • subscription.created — new subscriber activated
  • subscription.cancelled — subscription cancelled by user or merchant
  • subscription.expired — subscription expired after grace period
  • charge.succeeded — recurring payment collected
  • charge.failed — payment attempt failed

Webhook payload format

json
{
  "id": "evt_a3f9b2c1d4e5...",
  "type": "charge.succeeded",
  "timestamp": "2025-01-15T12:00:00.000Z",
  "data": {
    "subscriber": "0xabc...",
    "planId": "1",
    "amount": "1000000",
    "keeper": "0xdef..."
  }
}

Signature verification

Every delivery includes an X-Cron-Signature header. Verify it on your server using the HMAC-SHA256 shared secret:

typescript
import { verifySignature } from "@cron/webhooks";

// In your webhook handler (Next.js Route Handler example)
export async function POST(req: Request) {
  const rawBody = await req.text();
  const sig = req.headers.get("X-Cron-Signature") ?? "";

  const isValid = verifySignature(rawBody, sig, process.env.WEBHOOK_SECRET!);
  if (!isValid) return new Response("Unauthorized", { status: 401 });

  const event = JSON.parse(rawBody);
  // handle event.type ...
  return new Response("OK");
}

Running the webhook service

Set environment variables and run:

bash
# Required
CHAIN=base
RPC_URL=https://mainnet.base.org
WEBHOOK_URL=https://yourapp.com/webhooks/cron
WEBHOOK_SECRET=your-hmac-secret-min-32-chars

# Optional — filter by merchant address
WEBHOOK_MERCHANT=0xYOUR_MERCHANT_ADDRESS

# Optional — filter by event types (comma-separated)
WEBHOOK_EVENTS=charge.succeeded,charge.failed

# Optional — Push Protocol notifications
PUSH_CHANNEL_ADDRESS=0xYOUR_PUSH_CHANNEL
PUSH_CHANNEL_PRIVATE_KEY=0xYOUR_CHANNEL_KEY
PUSH_ENV=prod

npm run start:webhooks

Using as a library

typescript
import { WebhookService, EnvStore } from "@cron/webhooks";

const service = new WebhookService({
  chain: "base",
  rpcUrl: process.env.RPC_URL!,
  registryAddress: "0xYOUR_REGISTRY",
  endpoints: new EnvStore(), // or implement WebhookEndpointStore for DB-backed config
  maxAttempts: 3,
  retryDelayMs: 1000,
});

await service.start();

Retry logic

Failed deliveries are retried with exponential backoff. With the defaults (3 attempts, 1s base delay) the retry schedule is: immediate → 1s → 2s. After all attempts are exhausted, the event is logged and dropped.

[!] Production persistence

The default EnvStore holds endpoints in memory. For production, implement the WebhookEndpointStore interface backed by your database so merchants can register and manage their own webhook URLs.

Push Protocol notifications

When PUSH_CHANNEL_ADDRESS and PUSH_CHANNEL_PRIVATE_KEY are set, the service also sends wallet-native push notifications to the subscriber's address for every event. Subscribers receive these in the Push Protocol app, browser extension, or mobile app.