SubsManager
Webhooks & Event Streams
Receive real-time event notifications with cryptographic HMAC-SHA256 signature verification.
Webhooks & Event Streams
SubsManager delivers asynchronous, cryptographically signed webhooks to your server whenever critical lifecycle events occur (such as invoice payments, subscription state transitions, or renewal warnings).
1. Supported Event Catalog
| Event Name | Trigger Context | Recommended Action |
|---|---|---|
subscription.created | Subscription initialized (trialing or pending) | Provision user record in your local database. |
subscription.updated | State change (active, past_due, cancelled) | Grant, throttle, or revoke feature access. |
subscription.upcoming_renewal | 3 days before period end | Surface in-app banner reminding subscriber of renewal. |
invoice.created | New invoice generated (initial or renewal) | Record invoice in internal accounting ledger. |
invoice.paid | Invoice confirmed paid (EFT or Card) | Issue receipt to customer and extend active period. |
payment.created | One-time payment initialized | Log checkout attempt. |
payment.updated | Payment transitioned to completed or failed | Fulfill digital goods or alert customer to card decline. |
2. Cryptographic Signature Verification (HMAC-SHA256)
Every webhook request transmitted by SubsManager includes an x-subsmanager-signature HTTP header. This signature is an HMAC-SHA256 hash of the exact raw request body calculated with your application's webhookSecret (whsec_...).
Timing-Safe Comparison
Always perform string comparison using constant-time equality checks (such as crypto.timingSafeEqual) to prevent timing attacks.
Node.js / Express Example
const crypto = require('crypto');
const express = require('express');
const app = express();
app.post('/api/webhooks/subsmanager', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-subsmanager-signature'];
const secret = process.env.SUBSMANAGER_WEBHOOK_SECRET;
if (!signature) {
return res.status(401).send('Missing signature');
}
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
const isValid = crypto.timingSafeEqual(
Buffer.from(signature, 'utf8'),
Buffer.from(expectedSignature, 'utf8')
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(req.body.toString('utf8'));
const { event, data } = payload;
if (event === 'subscription.updated' && data.status === 'active') {
// Unlock premium SaaS features for subscriber
console.log(`Activated subscription for client: ${data.clientId}`);
}
res.status(200).json({ received: true });
});Next.js Route Handler Example (App Router)
// app/api/webhooks/subsmanager/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
export async function POST(req: NextRequest) {
const signature = req.headers.get('x-subsmanager-signature');
const secret = process.env.SUBSMANAGER_WEBHOOK_SECRET!;
const rawBody = await req.text();
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
if (signature !== expectedSignature) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
const { event, data } = JSON.parse(rawBody);
if (event === 'invoice.paid') {
console.log(`Invoice ${data.invoiceNumber} paid: ${data.currency} ${data.amount}`);
}
return NextResponse.json({ received: true });
}