Revoking Software Licenses on Stripe Cancellation
Cancelling a customer's subscription should immediately revoke their software access. Here's how to wire Stripe's customer.subscription.deleted event to Keymint's POST /key/block endpoint so it happens automatically (full Stripe setup in our Stripe subscription licensing guide).
How It Works
- Customer cancels their Stripe subscription
- Stripe sends
customer.subscription.deletedto your webhook handler - Your handler looks up the license key linked to that Stripe customer
- Your handler calls Keymint's
POST /key/blockwith the license key - The next time the customer's app checks in via
POST /key/activate, Keymint returns a 403
Step 1: Store the Stripe → License Mapping
When you create a license after a Stripe payment, save the relationship. A single SQL table is all you need: stripe_customer_id, stripe_subscription_id, license_key.
await db.stripeLicenseMap.create({
data: {
stripeCustomerId: session.customer,
stripeSubscriptionId: session.subscription,
licenseKey: newlyCreatedLicense.key,
},
});Step 2: Listen for Cancellation
Keymint's own Stripe integration handles customer.subscription.deleted by deactivating the org's billing plan. But it does not automatically block your customer's license keys — that's your business logic.
// app/api/webhooks/stripe-revoke/route.ts
const admin = new KeyMint(process.env.KEYMINT_ADMIN_API_KEY);
export async function POST(req: NextRequest) {
const event = stripe.webhooks.constructEvent(
await req.text(),
req.headers.get('stripe-signature')!,
process.env.STRIPE_WEBHOOK_SECRET!,
);
if (event.type === 'customer.subscription.deleted') {
const subscription = event.data.object;
const mappings = await db.stripeLicenseMap.findMany({
where: { stripeCustomerId: subscription.customer },
});
for (const { licenseKey } of mappings) {
await admin.blockKey({
productId: process.env.KEYMINT_PRODUCT_ID!,
licenseKey,
});
}
}
return Response.json({ received: true });
}Step 3: Handle Reactivation
If the customer resubscribes, unblock the license when invoice.paid fires — same pattern, but call admin.unblockKey() instead.
Step 4: Test
stripe listen --forward-to localhost:3000/api/webhooks/stripe-revoke
stripe trigger customer.subscription.deletedThen verify:
curl -X POST https://api.keymint.dev/key/activate \
-H "Authorization: Bearer YOUR_CLIENT_API_KEY" \
-d '{"productId":"prod_xxx","licenseKey":"KM-XXXX-XXXX-XXXX","hostId":"test"}'
# → { "message": "Invalid license key or insufficient permissions" }Production Notes
- Stripe delivers webhooks out of order — handle each event idempotently
- Add a 3-7 day grace period before blocking to survive Stripe's dunning retries
- Subscribe to Keymint's
license.disabledoutbound webhook as secondary confirmation - Log every block/unblock in your own database for customer support
Full API reference: keymint.dev/docs.