Guide to PayPro Global Webhooks: Features and Best Practices
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
| Feature | Details |
|---|---|
| Delivery | HTTP POST, form-encoded |
| Layer 1 | IP allowlist of fixed PayPro Global IPs |
| Layer 2 | HASH = MD5(OrderId + SecretKey) for real orders (test orders use MD5("1")) |
| Layer 3 | SIGNATURE = SHA256 of a fixed 7-field ordered concatenation |
| Keys | SecretKey (for HASH) and VALIDATION_KEY (for SIGNATURE) are different keys |
| Configuration | IPN URL per product, or Store Settings > Notifications |
| SDK | None (the npm/pip "paypro" packages are an unrelated company) |
Common events
PayPro Global IPN types include (note the deliberately non-standard SubscriptionChargeSucceed spelling):
| Event | Fires when |
|---|---|
OrderCharged | An order is charged |
OrderRefunded | An order is refunded |
OrderPartiallyRefunded | An order is partially refunded |
OrderChargedBack | An order is charged back |
OrderDeclined | An order is declined |
SubscriptionChargeSucceed | A subscription charge succeeds (non-standard spelling) |
SubscriptionChargeFailed | A subscription charge fails |
SubscriptionSuspended | A subscription is suspended |
SubscriptionRenewed | A subscription renews |
SubscriptionTerminated | A 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
SecretKeyfor theHASHandVALIDATION_KEYfor theSIGNATURE, 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
MD5andSHA256values over the documented concatenations and compare theHASHandSIGNATUREfields.
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_NAMEvalues, 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.