Link
✎

PayPro Global Licensing Integration

In short: PayPro Global sends form-encoded IPNs with a SHA-256 SIGNATURE. Your receiver creates a license for the initial order and one for each recurring charge, and deactivates it on termination, refund or chargeback.

PayPro Global describes itself as an “all-in-one platform for selling SaaS, software and video games”. Its webhook, also called IPN, is configured in your PayPro Global account.

What PayPro Global sends

PayPro Global sends an HTTPS POST with Content-Type: application/x-www-form-urlencoded. The event is in IPN_TYPE_NAME. Documented fields include ORDER_ID, PRODUCT_ID, ORDER_ITEM_SKU, CUSTOMER_EMAIL, CUSTOMER_FIRST_NAME, CUSTOMER_LAST_NAME, SUBSCRIPTION_ID, SUBSCRIPTION_INITIAL_ORDER_ID, ORDER_TOTAL_AMOUNT, ORDER_CURRENCY_CODE and ACTION_REASON (the reason for a refund, chargeback, decline or cancellation).

Map PayPro Global events to NetLicensing

PayPro Global event What to do in NetLicensing
OrderCharged A one-time product or the initial order of a subscription: Create a licensee and create a license from the plan’s template
SubscriptionChargeSucceed A recurring payment was processed: create one license for the next period, numbered with the ORDER_ID
SubscriptionRenewed The subscription was renewed by the customer or by PayPro Global support. PayPro Global does not say whether a renewal also sends SubscriptionChargeSucceed; with the ORDER_ID as license number, a second create for the same order fails as a duplicate
SubscriptionTerminated Deactivate the license (active=false)
SubscriptionSuspended Hold or deactivate the license until the subscription is active again
OrderRefunded, OrderPartiallyRefunded Deactivate the license (active=false) for a full refund
OrderChargedBack, OrderChargedBackWon Deactivate on chargeback; restore the license when the dispute is won
SubscriptionChargeFailed, OrderDeclined Your choice: keep access during the platform’s retry period, or deactivate

Some events (OrderOnWaiting, SubscriptionPaymentInfoChanged, InstantLeadNotification, and the bank-decline case of OrderDeclined) need extended webhooks; PayPro Global’s page says to contact the team.

Check that the request comes from PayPro Global

Each request carries a SIGNATURE parameter, documented as SHA256({ORDER_ID}+{ORDER_STATUS}+{ORDER_TOTAL_AMOUNT}+{CUSTOMER_EMAIL}+{VALIDATION_KEY}+{TEST_MODE}+{IPN_TYPE_NAME}): recompute it with your validation key and compare. Use the exact notation of PayPro Global’s webhook page. A HASH parameter (documented as an MD5 of the order id plus the secret key) is also sent, but prefer the SHA256 signature. The page also lists fixed source IP addresses you can allow.

Retries and duplicates

Return HTTP 200. PayPro Global retries the call to your webhook URL every 30 minutes, for a maximum of 3 attempts. (The retries mentioned in the subscription documentation are charge attempts to the customer, not webhooks.)

Match the customer and the plan

Identify the buyer by CUSTOMER_EMAIL, the subscription by SUBSCRIPTION_ID, and the product by PRODUCT_ID or ORDER_ITEM_SKU; map it to your licenseTemplateNumber with a lookup table. The payload also has ORDER_CUSTOM_FIELDS (“custom fields used in the order”) and CHECKOUT_QUERY_STRING; the page we read does not say how a merchant sets custom fields, so test before you use them to carry a licensee number.

Whatever ids you pick, use them as the NetLicensing numbers so later events can address the licensee and license directly and retries cannot create duplicates; see Use the platform’s IDs as NetLicensing numbers.

Example: the calls

PayPro Global               Your receiver                 NetLicensing
|                           |                             |
| OrderCharged ------------>|                             |
|                           | verify SIGNATURE            |
|                           | POST /licensee ------------>|
|                           |  number=customer            |
|                           | POST /license ------------->|
|                           |  number=ORDER_ID            |
|< 2xx ---------------------|                             |
|                           |                             |

The values below are illustrative; field names follow the vendor documentation listed under Sources. Form-encoded bodies are shown decoded for readability. Code samples are Python, using only the standard library unless noted.

1. PayPro Global calls your receiver

POST /webhooks/payproglobal HTTP/1.1
Content-Type: application/x-www-form-urlencoded

IPN_TYPE_NAME=SubscriptionChargeSucceed&ORDER_ID=1234567&ORDER_STATUS=Processed&ORDER_TOTAL_AMOUNT=49.00&CUSTOMER_EMAIL=jane%40example.com&SUBSCRIPTION_ID=7654&PRODUCT_ID=98765&TEST_MODE=0&SIGNATURE=<value>

2. Your receiver checks the request

import hashlib, hmac

from urllib.parse import parse_qsl

def verify_paypro(raw_body: bytes, validation_key: str) -> bool:
    f = dict(parse_qsl(raw_body.decode("utf-8"), keep_blank_values=True))
    base = (f["ORDER_ID"] + f["ORDER_STATUS"] + f["ORDER_TOTAL_AMOUNT"] + f["CUSTOMER_EMAIL"]
            + validation_key + f["TEST_MODE"] + f["IPN_TYPE_NAME"])
    expected = hashlib.sha256(base.encode("utf-8")).hexdigest()   # plain SHA-256, not HMAC
    return hmac.compare_digest(expected, f.get("SIGNATURE", "").lower())

PayPro Global’s example is sha256("12345Processed9.99test@payproglobal.comqwerty1OrderCharged"): the values are concatenated without separators and hashed with plain SHA-256. Recompute SIGNATURE = SHA256({ORDER_ID}+{ORDER_STATUS}+{ORDER_TOTAL_AMOUNT}+{CUSTOMER_EMAIL}+{VALIDATION_KEY}+{TEST_MODE}+{IPN_TYPE_NAME}) with your validation key, using the exact notation on PayPro Global’s webhook page, and compare. Test the comparison with a sandbox IPN before you go live.

3. Your receiver calls NetLicensing

NLIC=https://go.netlicensing.io/core/v2/rest
AUTH="apiKey:$NETLICENSING_API_KEY"    # API key with ROLE_APIKEY_OPERATION

# OrderCharged: the customer email (or your own id) becomes the licensee number
curl --user $AUTH --header "Accept: application/json" \
  --data "productNumber=PQVJQ5F7H&number=jane%40example.com&name=Jane%20Doe" \
  $NLIC/licensee

# OrderCharged / SubscriptionChargeSucceed: one license per order
curl --user $AUTH --header "Accept: application/json" \
  --data "number=1234567&licenseeNumber=jane%40example.com&licenseTemplateNumber=EUJK1BXQ52" \
  $NLIC/license

# OrderRefunded or OrderChargedBack
curl --user $AUTH --header "Accept: application/json" \
  --data "active=false" \
  $NLIC/license/1234567

PQVJQ5F7H and EUJK1BXQ52 stand for your product and license template numbers. A create with a number that already exists returns HTTP 400 with DuplicateEntityException. That makes both calls safe to repeat: send the licensee create with every paid event and continue when it reports a duplicate, and treat a duplicate license as “already done” and answer the platform with 2xx (see IDs as numbers).

Sources


You run this integration yourself; NetLicensing does not host a PayPro Global connector. The PayPro Global details on this page are based on the sources above, and the NetLicensing calls it relies on were tested against the live API. Vendors change their APIs, so verify the details against the linked pages before you go live. Back to Billing and Commerce Platforms.