JVZoo Knowledgebase

How can we help?

JVZIPN v2 – How to Receive JVZoo Order Data in Your Script (Updated for Payouts)

**This is an advanced topic. This is only recommended for vendors with advanced programming knowledge or those that have the assistance of a programmer.

JVZIPN v2

The modern JVZoo Instant Payment Notification system — richer data, simpler verification, and a complete payout breakdown in every transaction event.

How It Works

When a transaction event occurs, JVZoo sends an HTTP POST to the IPN v2 URL you configure in your product settings. Your script verifies the request, then processes it.

You must return HTTP 200 to acknowledge receipt. JVZoo will notify you by email if your endpoint cannot be reached or returns an error.

V1 and V2 are independent — you can configure one, both, or neither on each product. They fire from separate URLs and use different verification algorithms.

Setup

  1. In JVZoo, go to your product's Advanced Settings and select JVZIPN v1 / JVZIPN v2 / KeyGen / External Program
  2. Enter your endpoint URL in the JVZIPN V2 URL field
  3. Set a Secret Key — used to verify incoming requests. Navigate to My Account → Seller Settings to create one
  4. Save your settings
Your endpoint must be reachable over HTTPS and respond within 30 seconds. You can configure multiple URLs by separating them with commas.

Transaction Types

transaction_type Description What to do
SALE New purchase completed Grant access, send welcome email
BILL Recurring subscription rebilled Extend access period
RFND Transaction refunded or chargeback filed Revoke access. Note: chargebacks also arrive as RFND — v2 does not distinguish between the two

POST Fields

All fields are sent as application/x-www-form-urlencoded. Amounts are in dollars (not pennies — this differs from v1).

Transaction

Field Type Description Example
paykey string Unique JVZoo payment key AP-ABC123XYZ
prekey string Pre-approval key (recurring only) PRE-XYZ789
transaction_id string Receipt number. Rebills append -B001, -B002, etc. ABC123XYZ
transaction_type string SALE, BILL, or RFND SALE
total string Transaction amount in dollars 47.00
status string Transaction status Completed
date string Transaction date (used in verification) 2024-04-06 14:30:00
payment_method string Payment processor code PYPL
cverify string Verification hash — always validate this A1B2C3D4

Product

Field Type Description Example
product_id string JVZoo product ID 12345
product_name string Product name Premium Course
product_type string STANDARD or RECURRING STANDARD

Customer

Field Type Description Example
customer_email string Customer email address buyer@example.com
customer_first_name string First name John
customer_last_name string Last name Doe
customer_ip string IP address at time of purchase 203.0.113.42
customer_phone string Phone number if collected +1-555-123-4567

Vendor

Field Type Description Example
vendor_id string Vendor user ID 11111
vendor_name string Vendor display name ACME Corp
vendor_email string Vendor email vendor@example.com
paypal_email string Customer's payment email (PayPal transactions) buyer-paypal@example.com

Affiliate

Field Type Description Example
affiliate_id string Affiliate user ID (0 if none) 67890
affiliate_name string Affiliate display name Jane Smith
affiliate_email string Affiliate email affiliate@example.com
tid string Affiliate tracking ID campaign123
other_params string Custom data from your buy link (same as cvendthru in v1) userid=42

Tax & Fees

Field Type Description Example
tax_total string Total tax 4.50
vat_tax string EU VAT 3.20
national_sales_tax string Country sales tax 1.30
international_sales_tax string International tax 0.00
shipping_fee string Shipping fee 5.99

Recurring Subscription

These fields are only populated for RECURRING products.

Field Type Description Example
start_date string Subscription start date 2024-04-06 00:00:00
end_date string Subscription end date (empty if open-ended) 2025-04-06 00:00:00
next_payment_date string Next rebill date 2024-05-06 00:00:00

Delivery Address

Only populated when the product collects shipping information.

Field Description
delivery_name Recipient name
delivery_phone Phone number
delivery_address_line_1 Street address
delivery_address_line_2 Apartment/suite
delivery_city City
delivery_region State or region
delivery_country Country
delivery_postal_code Postal code

Transaction Payouts

transactionPayouts is a JSON-encoded string within the form body. It is only present when payout data exists. Always check before parsing.

JSON[
  {
    "payee_amount": 33.60,
    "payee_user_id": 11111,
    "payee_name": "ACME Corp",
    "payout_type": "VENDOR",
    "payment_processor": "PayPal",
    "payout_status": "Settled"
  },
  {
    "payee_amount": 9.40,
    "payee_user_id": 67890,
    "payee_name": "Jane Smith",
    "payout_type": "AFFILIATES",
    "payment_processor": "PayPal",
    "payout_status": "Settled"
  },
  {
    "payee_amount": 4.00,
    "payee_user_id": 1,
    "payee_name": "JVZoo",
    "payout_type": "JVZOO",
    "payment_processor": "JVZoo",
    "payout_status": "Settled"
  }
]

Sample Payload

A typical JVZIPN v2 POST (values sanitized):

POST bodycustomer_first_name: Jamie
customer_last_name: Rivers
customer_email: jamie.rivers@example.com

delivery_name: Jamie Rivers
delivery_phone: +1-843-555-0199
delivery_address_line_1: 742 Evergreen Lane
delivery_address_line_2:
delivery_city: Seaside
delivery_region: FL
delivery_postal_code: 33004
delivery_country: UNITED_STATES

product_id: 20455
product_name: Premium Webinar Toolkit
product_type: STANDARD

transaction_type: SALE
transaction_id: 9TX000111Z999000A
paykey: PT-9TX000111Z999000A

affiliate_id: 17
affiliate_name: Growth Labs LLC
affiliate_email: partners@growthlabs.example

vendor_id: 42
vendor_name: Acme Digital Corp
vendor_email: billing@acmedigital.example

total: 97.00
payment_method: PYPL
status: COMPLETED
date: 2024-09-11 12:16:42

tax_total: 0.00
shipping_fee: 0.00

cverify: 8F9C3A12
other_params:
next_payment_date:
start_date:
end_date:

transactionPayouts: [...]

Verifying Requests

🔒Always verify cverify before processing. V2 uses a simpler algorithm than v1 — only 5 fields are hashed.

Algorithm

  1. Concatenate these 5 fields in this exact order, separated by pipes, with a trailing pipe after the last field:

    paykey|customer_email|product_name|transaction_type|date|
  2. Append your Secret Key (no pipe after the secret)
  3. Convert to UTF-8
  4. Compute SHA-1
  5. Take the first 8 characters, uppercase

PHP Example

PHP<?php
$secretKey = 'your_secret_key_here';

$paykey           = $_POST['paykey'] ?? '';
$customer_email   = $_POST['customer_email'] ?? '';
$product_name     = $_POST['product_name'] ?? '';
$transaction_type = $_POST['transaction_type'] ?? '';
$date             = $_POST['date'] ?? '';
$incoming_verify  = $_POST['cverify'] ?? '';

$string = $paykey . '|'
    . $customer_email . '|'
    . $product_name . '|'
    . $transaction_type . '|'
    . $date . '|'
    . $secretKey;

$calculated = strtoupper(substr(sha1(mb_convert_encoding($string, 'UTF-8')), 0, 8));

if ($calculated !== $incoming_verify) {
    http_response_code(403);
    die('Verification failed');
}

// Verified — process the transaction
$type   = $_POST['transaction_type'];
$email  = $_POST['customer_email'];
$amount = $_POST['total']; // Already in dollars

// Parse payouts if present
$payouts = [];
if (!empty($_POST['transactionPayouts'])) {
    $payouts = json_decode($_POST['transactionPayouts'], true);
}

switch ($type) {
    case 'SALE':
        grantAccess($email);
        break;
    case 'RFND':
        revokeAccess($email);
        break;
    case 'BILL':
        extendAccess($email, $_POST['next_payment_date']);
        break;
}

http_response_code(200);
echo 'OK';

Python Example

Pythonimport hashlib
import json
from flask import Flask, request

app = Flask(__name__)
SECRET_KEY = 'your_secret_key_here'

@app.route('/ipn/v2', methods=['POST'])
def handle_ipn_v2():
    data = request.form

    string = '|'.join([
        data.get('paykey', ''),
        data.get('customer_email', ''),
        data.get('product_name', ''),
        data.get('transaction_type', ''),
        data.get('date', ''),
    ]) + '|' + SECRET_KEY

    calculated = hashlib.sha1(string.encode('utf-8')).hexdigest()[:8].upper()

    if calculated != data.get('cverify', ''):
        return 'Verification failed', 403

    txn_type = data.get('transaction_type')
    email    = data.get('customer_email')
    amount   = data.get('total')  # Already in dollars

    # Parse payouts if present
    payouts = []
    if data.get('transactionPayouts'):
        payouts = json.loads(data['transactionPayouts'])

    if txn_type == 'SALE':
        grant_access(email)
    elif txn_type == 'RFND':
        revoke_access(email)
    elif txn_type == 'BILL':
        extend_access(email)

    return 'OK', 200

Working with transactionPayouts

Loop through the payouts array to record who received what on each transaction — useful for accounting, reporting, and affiliate reconciliation.

PHP

PHP$payouts = isset($_POST['transactionPayouts'])
    ? json_decode($_POST['transactionPayouts'], true)
    : [];

foreach ($payouts as $payout) {
    $amount    = $payout['payee_amount']      ?? 0;
    $payeeId   = $payout['payee_user_id']     ?? null;
    $payeeName = $payout['payee_name']        ?? '';
    $type      = $payout['payout_type']       ?? '';
    $processor = $payout['payment_processor'] ?? '';
    $status    = $payout['payout_status']     ?? '';

    // store or process each payout row
}

Python

Pythonpayouts_raw = data.get('transactionPayouts', '')
payouts = json.loads(payouts_raw) if payouts_raw else []

for payout in payouts:
    amount    = payout.get('payee_amount')
    payee_id  = payout.get('payee_user_id')
    payee     = payout.get('payee_name')
    ptype     = payout.get('payout_type')
    processor = payout.get('payment_processor')
    status    = payout.get('payout_status')

Migrating from v1

If you already have a v1 handler, this table shows how v1 fields map to their v2 equivalents:

v1 Field v2 Equivalent Notes
cproditem product_id
cprodtitle product_name
cprodtype product_type
ctransaction transaction_type
ctransreceipt transaction_id
ctransamount total v1 is in pennies, v2 is in dollars
ctranspaymentmethod payment_method
ctranstime date v1 is Unix epoch, v2 is formatted date string
ctransvendor vendor_id
ctransaffiliate affiliate_id
caffitid tid
cvendthru other_params
ccustemail customer_email
ccustname customer_first_name + customer_last_name v2 splits into separate fields
No equivalent transactionPayouts New in v2 — structured payout breakdown
💡
Recommended migration approach:
Keep your v1 handler running for existing products. Detect v2 payloads by checking for transactionPayouts or customer_first_name fields. Run v1 and v2 handlers in parallel during rollout and compare results.

FAQ

Will every order have a transactionPayouts array?
Typically yes — most orders include at least a vendor payout row. Many include vendor, affiliate, and platform rows. However, always check for the field's presence before parsing since it can be absent.
Can I ignore transactionPayouts?
Yes. If you don't need payout breakdown data for reporting or reconciliation, you can safely ignore it and process only the core transaction fields.
Is verification required?
Yes. Never grant access or fulfil an order unless your calculated cverify matches the incoming value. Without verification, anyone could POST fake transaction data to your endpoint.
Does it matter which version of the secret key I use?
The secret key is the same for v1 and v2 — it's set once at the account level in My Account → Seller Settings. The difference is in how it's applied: v1 hashes all fields alphabetically, v2 hashes only 5 specific fields.

Key Points

total is in dollars, not pennies — this is different from v1's ctransamount
date format matters for verification — use the exact string from the POST, do not reformat it
transactionPayouts is a JSON string inside form data — call json_decode() / json.loads() on it separately
transactionPayouts may be absent — always check before parsing
other_params is the v2 equivalent of cvendthru in v1 — custom passthrough data from your buy link
Funnel products fire independently — each step sends its own IPN. There is no funnel_id in the payload
No retry on failure — if your endpoint is down when JVZoo fires the IPN, the notification is lost. To recover missed transactions: poll the JVZoo REST API to reconcile, or contact JVZoo support to manually resend specific transactions
Always return HTTP 200 — JVZoo will email you on failure but does not retry automatically
Was this article helpful?
0 out of 0 found this helpful
Have more questions? Submit a request
SUBMIT A TICKET