JVZoo Integration Specialist
Expert guidance for building production-ready JVZoo integrations that handle purchases, license management, and account creation.
Overview
JVZoo integration involves three core components:
- •IPN (Instant Payment Notification) - Real-time webhook from JVZoo when purchases occur
- •License Management - Generate, validate, and track software licenses
- •Account Creation - Automatically create user accounts from purchase data
IPN Setup & Security
IPN Endpoint Requirements
Your IPN endpoint must:
- •Accept POST requests from JVZoo servers
- •Respond within 10 seconds with HTTP 200
- •Validate authenticity using secret key
- •Handle idempotent processing (duplicates possible)
- •Log all requests for debugging
IPN Verification
Always verify IPN authenticity using SHA-1 hash:
Verification Hash = SHA1(secretKey + "|" + transactionId + "|" + amount + "|" + productId)
Compare this hash with the cverify parameter sent by JVZoo. Never process unverified IPNs.
Essential IPN Parameters
Transaction Info:
- •
ctransaction- Transaction ID (use as idempotency key) - •
ctransaction_type- SALE, RFND (refund), CGBK (chargeback), INSTAL (recurring), CANCEL-REBILL - •
ctransreceipt- JVZoo receipt number - •
cproditem- Product ID (your product identifier) - •
cprodtype- STANDARD, RECURRING, etc.
Customer Info:
- •
ccustemail- Customer email (primary identifier) - •
ccustname- Customer full name - •
ccustcc- Customer country code - •
ccuststate- Customer state/region
Financial Info:
- •
ctransamount- Sale amount - •
ctransaffiliate- Affiliate commission - •
ctransvendor- Your earnings
Verification:
- •
cverify- SHA-1 hash for verification
Account Creation Workflow
Step 1: Verify IPN
function verifyIPN(ipnData, secretKey) {
const { ctransaction, ctransamount, cproditem, cverify } = ipnData;
const hash = crypto
.createHash('sha1')
.update(`${secretKey}|${ctransaction}|${ctransamount}|${cproditem}`)
.digest('hex')
.toUpperCase();
return hash === cverify.toUpperCase();
}
Step 2: Check for Duplicate Processing
async function isDuplicateTransaction(transactionId) {
// Query database for existing transaction
const existing = await db.transactions.findOne({
jvzoo_transaction_id: transactionId
});
return !!existing;
}
Step 3: Create or Update User Account
async function processNewSale(ipnData) {
const { ccustemail, ccustname, cproditem, ctransaction } = ipnData;
// Find or create user
let user = await db.users.findOne({ email: ccustemail });
if (!user) {
user = await db.users.create({
email: ccustemail,
name: ccustname,
created_via: 'jvzoo',
jvzoo_transaction: ctransaction
});
}
return user;
}
Step 4: Generate License Key
function generateLicenseKey(userId, productId, transactionId) {
const timestamp = Date.now();
const data = `${userId}:${productId}:${transactionId}:${timestamp}`;
const hash = crypto
.createHash('sha256')
.update(data + process.env.LICENSE_SECRET)
.digest('hex');
// Format: XXXX-XXXX-XXXX-XXXX
return hash.substring(0, 16).toUpperCase().match(/.{1,4}/g).join('-');
}
Step 5: Store License & Send Access
async function createLicense(userId, productId, ipnData) {
const licenseKey = generateLicenseKey(
userId,
productId,
ipnData.ctransaction
);
const license = await db.licenses.create({
user_id: userId,
product_id: productId,
license_key: licenseKey,
jvzoo_transaction: ipnData.ctransaction,
jvzoo_receipt: ipnData.ctransreceipt,
status: 'active',
purchase_date: new Date(),
transaction_type: ipnData.ctransaction_type
});
// Send email with license key and login details
await sendWelcomeEmail(userId, licenseKey);
return license;
}
Transaction Type Handling
SALE (New Purchase)
case 'SALE': await processNewSale(ipnData); await createLicense(user.id, ipnData.cproditem, ipnData); break;
RFND (Refund)
case 'RFND':
await db.licenses.update(
{ jvzoo_transaction: ipnData.ctransaction },
{ status: 'refunded', refunded_at: new Date() }
);
await revokeAccess(user.id);
break;
CGBK (Chargeback)
case 'CGBK':
await db.licenses.update(
{ jvzoo_transaction: ipnData.ctransaction },
{ status: 'chargeback', chargeback_at: new Date() }
);
await revokeAccess(user.id);
break;
INSTAL (Recurring Payment)
case 'INSTAL':
await db.licenses.update(
{ jvzoo_transaction: ipnData.ctransaction },
{
status: 'active',
last_payment: new Date(),
payment_count: { $inc: 1 }
}
);
break;
CANCEL-REBILL (Subscription Cancelled)
case 'CANCEL-REBILL':
await db.licenses.update(
{ jvzoo_transaction: ipnData.ctransaction },
{
status: 'cancelled',
cancelled_at: new Date()
}
);
// Grace period before revoking access
await scheduleAccessRevocation(user.id, 30); // 30 days
break;
License Validation API
Build an API endpoint your software calls to validate licenses:
// POST /api/validate-license
async function validateLicense(req, res) {
const { license_key, email } = req.body;
const license = await db.licenses.findOne({
license_key: license_key,
status: 'active'
});
if (!license) {
return res.json({ valid: false, error: 'Invalid license' });
}
const user = await db.users.findById(license.user_id);
if (user.email !== email) {
return res.json({ valid: false, error: 'Email mismatch' });
}
// Update last validation timestamp
await db.licenses.update(
{ _id: license._id },
{ last_validated: new Date() }
);
return res.json({
valid: true,
license: {
product_id: license.product_id,
expiry_date: license.expiry_date,
features: license.features
}
});
}
Database Schema Recommendations
Users Table
CREATE TABLE users ( id UUID PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(255), created_via VARCHAR(50), jvzoo_transaction VARCHAR(255), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() );
Licenses Table
CREATE TABLE licenses ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), product_id VARCHAR(100) NOT NULL, license_key VARCHAR(50) UNIQUE NOT NULL, jvzoo_transaction VARCHAR(255) UNIQUE NOT NULL, jvzoo_receipt VARCHAR(255), status VARCHAR(50) NOT NULL, -- active, refunded, chargeback, cancelled purchase_date TIMESTAMP NOT NULL, expiry_date TIMESTAMP, last_validated TIMESTAMP, transaction_type VARCHAR(50), refunded_at TIMESTAMP, chargeback_at TIMESTAMP, cancelled_at TIMESTAMP, payment_count INTEGER DEFAULT 1, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() );
Transactions Table (Audit Log)
CREATE TABLE jvzoo_transactions ( id UUID PRIMARY KEY, jvzoo_transaction_id VARCHAR(255) UNIQUE NOT NULL, transaction_type VARCHAR(50) NOT NULL, product_id VARCHAR(100) NOT NULL, customer_email VARCHAR(255) NOT NULL, amount DECIMAL(10,2), raw_ipn_data JSONB, processed BOOLEAN DEFAULT FALSE, processed_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW() );
Product ID Mapping
Map JVZoo product IDs to your internal product identifiers:
const PRODUCT_MAPPING = {
'123456': {
name: 'AI Ranker Pro - Basic',
features: ['feature1', 'feature2'],
internal_id: 'ai-ranker-basic'
},
'123457': {
name: 'AI Ranker Pro - Premium',
features: ['feature1', 'feature2', 'feature3', 'feature4'],
internal_id: 'ai-ranker-premium'
},
'123458': {
name: 'Callfluent AI - Starter',
features: ['100-calls', 'basic-voices'],
internal_id: 'callfluent-starter'
}
};
function getProductConfig(jvzooProductId) {
return PRODUCT_MAPPING[jvzooProductId] || null;
}
Security Best Practices
Environment Variables
Never hardcode credentials:
JVZOO_SECRET_KEY=your_secret_key_here LICENSE_SECRET=separate_key_for_license_generation DATABASE_URL=your_database_connection SMTP_API_KEY=email_service_key
Rate Limiting
Protect your IPN endpoint from abuse:
const rateLimit = require('express-rate-limit');
const ipnLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Max 100 requests per IP
message: 'Too many requests from this IP'
});
app.post('/ipn', ipnLimiter, handleIPN);
Input Validation
Always validate and sanitize IPN data:
function sanitizeIPNData(data) {
return {
ctransaction: String(data.ctransaction || '').trim(),
ccustemail: String(data.ccustemail || '').toLowerCase().trim(),
ccustname: String(data.ccustname || '').trim(),
cproditem: String(data.cproditem || '').trim(),
ctransamount: parseFloat(data.ctransamount || 0),
// ... sanitize all fields
};
}
Error Handling
Always return 200 OK to prevent retries, but log errors:
app.post('/ipn', async (req, res) => {
try {
// Always acknowledge receipt
res.status(200).send('OK');
// Process asynchronously
await processIPN(req.body);
} catch (error) {
// Log error but don't tell JVZoo about it
console.error('IPN Processing Error:', error);
await logError(error, req.body);
}
});
Testing Your Integration
Test Mode
JVZoo provides test IPNs. Look for test indicators:
function isTestTransaction(ipnData) {
return ipnData.ctransaction.toLowerCase().includes('test') ||
ipnData.ccustemail.toLowerCase().includes('test');
}
Manual IPN Testing
Send test IPN to your endpoint:
curl -X POST https://yoursite.com/ipn \ -d "ctransaction=TEST123456" \ -d "ccustemail=test@example.com" \ -d "ccustname=Test User" \ -d "cproditem=123456" \ -d "ctransamount=97.00" \ -d "ctransaction_type=SALE" \ -d "cverify=YOUR_CALCULATED_HASH"
Validation Checklist
- • IPN verification working correctly
- • Duplicate transactions prevented
- • User accounts created successfully
- • License keys generated and stored
- • Welcome emails sent
- • Refunds revoke access
- • Chargebacks logged
- • Recurring payments extend access
- • All transactions logged for audit
- • Error handling returns 200 OK
Common Implementation Frameworks
Node.js + Express
See references/nodejs-implementation.md for complete Express.js example with middleware, error handling, and database integration.
Python + FastAPI
See references/python-implementation.md for FastAPI implementation with async handling and Pydantic validation.
Next.js API Routes
See references/nextjs-implementation.md for serverless implementation using Next.js API routes with Supabase.
Troubleshooting
IPN not being received:
- •Check JVZoo IPN URL configuration in product settings
- •Verify endpoint is publicly accessible (not localhost)
- •Check firewall/server logs
- •Ensure endpoint responds within 10 seconds
Hash verification failing:
- •Verify secret key matches JVZoo dashboard
- •Check parameter order in hash calculation
- •Ensure string concatenation uses pipes (|)
- •Convert hash to uppercase for comparison
Duplicate accounts created:
- •Implement idempotency check using
ctransaction - •Use database constraints on transaction ID
- •Log all IPN requests to audit table
License validation failing:
- •Check license status in database
- •Verify email matches purchase email
- •Ensure license hasn't been refunded/charged back
- •Check for expiry dates on time-limited licenses
Quick Start Template
For fastest implementation, use scripts/setup_ipn_endpoint.js to generate boilerplate IPN handler with your framework of choice (Express, FastAPI, Next.js).
See references/deployment-guide.md for production deployment considerations including SSL, monitoring, logging, and backup strategies.