Web App Integration
Complete guide to integrating revnu payments into your web application using webhooks
Overview
revnu provides a webhook-based integration for web applications. When a customer makes a purchase or their subscription status changes, revnu sends a signed webhook to your application so you can grant or revoke access accordingly.
Key Features
- •Real-time purchase notifications via webhooks
- •Cryptographically signed payloads for security
- •Support for one-time purchases and subscriptions
- •Automatic subscription lifecycle management (renewals, cancellations, payment failures)
Integration Flow
- 1.Customer clicks a checkout link on your site
- 2.Customer completes payment on revnu/Stripe checkout
- 3.revnu sends a webhook to your server
- 4.Your server verifies the signature and grants access
- 5.For subscriptions, webhooks are sent on each renewal or status change
Webhook Events
Sent when a customer completes a purchase (one-time or first subscription payment).
{
"event": "purchase.completed",
"data": {
"purchaseId": "purch_abc123",
"buyerEmail": "customer@example.com",
"buyerName": "John Doe",
"productId": "prod_xyz789",
"productName": "Pro Plan",
"amountCents": 2999,
"currency": "usd",
"isSubscription": true,
"status": "active",
"currentPeriodEnd": "2024-02-15T00:00:00Z"
},
"timestamp": "2024-01-15T12:00:00Z"
}Sent when a subscription is cancelled.
{
"event": "purchase.cancelled",
"data": {
"purchaseId": "purch_abc123",
"buyerEmail": "customer@example.com",
"productId": "prod_xyz789",
"productName": "Pro Plan",
"status": "cancelled"
},
"timestamp": "2024-01-20T12:00:00Z"
}Sent when a recurring subscription payment fails.
{
"event": "payment.failed",
"data": {
"purchaseId": "purch_abc123",
"buyerEmail": "customer@example.com",
"productId": "prod_xyz789",
"productName": "Pro Plan",
"status": "past_due"
},
"timestamp": "2024-01-15T12:00:00Z"
}Signature Verification
All webhooks are signed with your webhook secret using HMAC-SHA256. The signature is included in the x-rev-signature header.
import crypto from "crypto";
export function verifyRevSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Complete Webhook Handler
Full example for Next.js App Router:
// app/api/webhooks/revnu/route.ts
import crypto from "crypto";
import { NextResponse } from "next/server";
interface RevnuWebhookPayload {
event: "purchase.completed" | "purchase.cancelled" | "payment.failed";
data: {
purchaseId: string;
buyerEmail: string;
buyerName?: string;
productId: string;
productName: string;
amountCents?: number;
currency?: string;
isSubscription?: boolean;
status?: "active" | "cancelled" | "past_due";
currentPeriodEnd?: string;
};
timestamp?: string;
}
function verifySignature(payload: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
export async function POST(request: Request) {
try {
const payload = await request.text();
const signature = request.headers.get("x-rev-signature") ?? "";
if (!verifySignature(payload, signature, process.env.REV_WEBHOOK_SECRET!)) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
const event: RevnuWebhookPayload = JSON.parse(payload);
switch (event.event) {
case "purchase.completed":
// Grant access to the user
// await db.user.update({
// where: { email: event.data.buyerEmail },
// data: { subscriptionStatus: "active", productId: event.data.productId }
// });
break;
case "purchase.cancelled":
// Revoke access
// await db.user.update({
// where: { email: event.data.buyerEmail },
// data: { subscriptionStatus: "cancelled" }
// });
break;
case "payment.failed":
// Notify user about payment failure
// await sendPaymentFailedEmail(event.data.buyerEmail);
break;
}
return NextResponse.json({ received: true });
} catch (error) {
console.error("Webhook error:", error);
return NextResponse.json({ error: "Webhook processing failed" }, { status: 500 });
}
}Environment Variables
Add these to your .env.local:
# revnu Integration
REV_API_KEY=rev_your_api_key
REV_WEBHOOK_SECRET=whsec_your_webhook_secretBest Practices
Idempotency
Webhooks may be delivered more than once. Make your handlers idempotent by checking if you've already processed a purchase using the purchaseId.
Error Handling
Return appropriate status codes so revnu knows whether to retry:200 for success,4xx for client errors (no retry),5xx for server errors (will retry).
Testing
Use the "Test" button in your revnu dashboard (Developers > Integration) to send a test webhook to your endpoint. For local development, use ngrok to expose your local server.
Rate Limits
Webhook deliveries are limited to prevent abuse:
- Maximum 10 retries per webhook
- Exponential backoff between retries (1s, 2s, 4s, 8s, etc.)
- Webhooks timeout after 30 seconds