| name | jvzoo-integration |
| description | Expert JVZoo integration specialist for building backend systems that handle JVZoo purchases, create user accounts, manage software licenses, and process IPN notifications. Use when implementing JVZoo product licensing, account creation workflows, payment processing, refund handling, or connecting software products to JVZoo marketplace by product ID. |
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-REBILLctransreceipt- JVZoo receipt numbercproditem- Product ID (your product identifier)cprodtype- STANDARD, RECURRING, etc.
Customer Info:
ccustemail- Customer email (primary identifier)ccustname- Customer full nameccustcc- Customer country codeccuststate- Customer state/region
Financial Info:
ctransamount- Sale amountctransaffiliate- Affiliate commissionctransvendor- 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.