Gareth Wilson Gareth Wilson

Guide to PayPro Global Webhooks: Features and Best Practices

Published


PayPro Global webhooks notify your application about order and subscription activity: an order is charged, refunded, or charged back, a subscription renews or is terminated. PayPro Global calls them IPN (Instant Payment Notification). If you're building on PayPro Global, IPN is how you react to these events without polling.

This guide covers how PayPro Global IPN works, the events you'll handle, how to verify the HASH and SIGNATURE parameters, and the best practices for production.

This covers PayPro Global (payproglobal.com). It is not the unrelated PayPro B.V. / paypro.nl, whose npm and pip "paypro" packages belong to a different company.

What are PayPro Global webhooks?

PayPro Global IPN messages are HTTP POSTs sent as form-encoded data. Verification is bespoke (not an HMAC header, not Standard Webhooks) and has three layers: an IP allowlist of fixed PayPro Global addresses, a HASH parameter, and a SIGNATURE parameter. Crucially, the two use different keys: HASH uses your SecretKey, and SIGNATURE uses your VALIDATION_KEY, both found under Store Settings > General Settings > Integration.

PayPro Global webhook features

FeatureDetails
DeliveryHTTP POST, form-encoded
Layer 1IP allowlist of fixed PayPro Global IPs
Layer 2HASH = MD5(OrderId + SecretKey) for real orders (test orders use MD5("1"))
Layer 3SIGNATURE = SHA256 of a fixed 7-field ordered concatenation
KeysSecretKey (for HASH) and VALIDATION_KEY (for SIGNATURE) are different keys
ConfigurationIPN URL per product, or Store Settings > Notifications
SDKNone (the npm/pip "paypro" packages are an unrelated company)

Common events

PayPro Global IPN types include (note the deliberately non-standard SubscriptionChargeSucceed spelling):

EventFires when
OrderChargedAn order is charged
OrderRefundedAn order is refunded
OrderPartiallyRefundedAn order is partially refunded
OrderChargedBackAn order is charged back
OrderDeclinedAn order is declined
SubscriptionChargeSucceedA subscription charge succeeds (non-standard spelling)
SubscriptionChargeFailedA subscription charge fails
SubscriptionSuspendedA subscription is suspended
SubscriptionRenewedA subscription renews
SubscriptionTerminatedA subscription is terminated

Match SubscriptionChargeSucceed exactly, it isn't spelled Succeeded.

Setting up PayPro Global webhooks

Set your IPN URL per product, or globally under Store Settings > Notifications. Retrieve both keys from Store Settings > General Settings > Integration: the SecretKey for the HASH check and the VALIDATION_KEY for the SIGNATURE check. Keep them straight, they are not interchangeable.

Securing PayPro Global webhooks

Verify all three layers. Confirm the source IP is on PayPro Global's allowlist. Check HASH against MD5(OrderId + SecretKey). Then recompute SIGNATURE as the SHA256 of the fields concatenated in this exact order: ORDER_ID + ORDER_STATUS + ORDER_TOTAL_AMOUNT + CUSTOMER_EMAIL + VALIDATION_KEY + TEST_MODE + IPN_TYPE_NAME. The concatenation order and the inclusion of TEST_MODE and IPN_TYPE_NAME are the easy parts to get wrong.

const crypto = require("crypto");

const SECRET_KEY = process.env.PAYPRO_SECRET_KEY; // for HASH
const VALIDATION_KEY = process.env.PAYPRO_VALIDATION_KEY; // for SIGNATURE

function md5(s) {
  return crypto.createHash("md5").update(s).digest("hex");
}
function sha256(s) {
  return crypto.createHash("sha256").update(s).digest("hex");
}

function verify(body) {
  // Layer 2: HASH = MD5(OrderId + SecretKey)
  const expectedHash = md5(`${body.ORDER_ID}${SECRET_KEY}`);

  // Layer 3: SIGNATURE = SHA256 of the fixed 7-field ordered concatenation
  const message =
    `${body.ORDER_ID}${body.ORDER_STATUS}${body.ORDER_TOTAL_AMOUNT}` +
    `${body.CUSTOMER_EMAIL}${VALIDATION_KEY}${body.TEST_MODE}${body.IPN_TYPE_NAME}`;
  const expectedSig = sha256(message);

  const hashOk = timingSafeEqual(expectedHash, body.HASH);
  const sigOk = timingSafeEqual(expectedSig, body.SIGNATURE);
  return hashOk && sigOk;
}

function timingSafeEqual(a, b) {
  const ba = Buffer.from(a || "");
  const bb = Buffer.from(b || "");
  return ba.length === bb.length && crypto.timingSafeEqual(ba, bb);
}

// Also confirm req.ip is on PayPro Global's fixed allowlist (Layer 1).
app.post("/webhook", express.urlencoded({ extended: false }), (req, res) => {
  if (!verify(req.body)) return res.sendStatus(401);
  res.sendStatus(200); // acknowledge fast
  processQueue.add(req.body); // branch on IPN_TYPE_NAME, async
});

The same verification in Python:

import hashlib
import hmac
import os

SECRET_KEY = os.environ["PAYPRO_SECRET_KEY"]  # for HASH
VALIDATION_KEY = os.environ["PAYPRO_VALIDATION_KEY"]  # for SIGNATURE


def verify(body: dict) -> bool:
    expected_hash = hashlib.md5(f"{body['ORDER_ID']}{SECRET_KEY}".encode()).hexdigest()

    message = (
        f"{body['ORDER_ID']}{body['ORDER_STATUS']}{body['ORDER_TOTAL_AMOUNT']}"
        f"{body['CUSTOMER_EMAIL']}{VALIDATION_KEY}{body['TEST_MODE']}{body['IPN_TYPE_NAME']}"
    )
    expected_sig = hashlib.sha256(message.encode()).hexdigest()

    return hmac.compare_digest(expected_hash, body.get("HASH", "")) and hmac.compare_digest(
        expected_sig, body.get("SIGNATURE", "")
    )

PayPro Global webhook limitations and pain points

Two different keys for the two checks

The Problem: HASH uses SecretKey and SIGNATURE uses VALIDATION_KEY. Swapping them makes one check pass and the other fail, or both fail confusingly.

Why It Happens: PayPro Global uses separate keys for the two layers.

Workarounds:

  • Keep SecretKey for the HASH and VALIDATION_KEY for the SIGNATURE, both from Store Settings > General Settings > Integration.

How Hookdeck Can Help: Hookdeck verifies provider signatures at the edge, so your app doesn't juggle two keys across two checks.

The SIGNATURE field order is exact

The Problem: SIGNATURE is the SHA256 of seven fields in a fixed order ending in TEST_MODE and IPN_TYPE_NAME. A wrong order, or omitting those trailing fields, never matches.

Why It Happens: The signed string is a positional concatenation.

Workarounds:

  • Concatenate exactly ORDER_ID + ORDER_STATUS + ORDER_TOTAL_AMOUNT + CUSTOMER_EMAIL + VALIDATION_KEY + TEST_MODE + IPN_TYPE_NAME.

How Hookdeck Can Help: Hookdeck reconstructs and verifies the signed string at the edge.

It's MD5 and SHA256, not an HMAC header

The Problem: The checks are plain MD5 and SHA256 over concatenations, not an HMAC in a header. Standard HMAC-header logic finds nothing to verify.

Why It Happens: PayPro Global uses hash concatenations as form parameters.

Workarounds:

  • Compute the MD5 and SHA256 values over the documented concatenations and compare the HASH and SIGNATURE fields.

How Hookdeck Can Help: Hookdeck handles the bespoke scheme at the edge, so your app receives pre-verified events.

The non-standard event spelling

The Problem: SubscriptionChargeSucceed is spelled deliberately without the -ed. A handler keyed to SubscriptionChargeSucceeded never fires.

Why It Happens: PayPro Global's event catalog uses that spelling.

Workarounds:

  • Use the exact IPN_TYPE_NAME values, and handle unknown types defensively.

How Hookdeck Can Help: Hookdeck routes on the exact IPN_TYPE_NAME you receive.

Best practices

Verify all three layers

Confirm the source IP, check HASH against MD5(OrderId + SecretKey), and recompute SIGNATURE as the SHA256 of the ordered 7-field concatenation.

Keep the two keys straight

SecretKey is for the HASH; VALIDATION_KEY is for the SIGNATURE.

Match event spellings exactly

Use SubscriptionChargeSucceed and the other exact IPN_TYPE_NAME values.

Return 200 and process asynchronously

Acknowledge quickly and defer work to a queue. See why to process webhooks asynchronously.

Make PayPro Global webhooks production-ready

Hookdeck verifies the HASH and SIGNATURE, deduplicates, and durably queues every IPN

Conclusion

PayPro Global IPN is verified in three layers: an IP allowlist, a HASH of MD5(OrderId + SecretKey), and a SIGNATURE that's the SHA256 of a fixed 7-field concatenation ending in TEST_MODE + IPN_TYPE_NAME using a separate VALIDATION_KEY. Keep the two keys straight, get the concatenation order right, match the non-standard event spellings, and acknowledge fast.

Hookdeck verifies the checks, deduplicates, and durably queues every IPN at the edge, so your app only ever processes verified events.

Get started with Hookdeck for free and handle PayPro Global webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.