Hosted Payment Page Integration
Build a complete HPP integration that creates a session, redirects the customer to a branded payment page, and receives a webhook when payment completes.
How it works
Prerequisites
- A CatalystPay API key (
rk_...) - A campaign ID with a hosted page configured (ask your CatalystPay admin)
- A publicly accessible webhook endpoint (use webhook.site for testing)
Step 1: Create a payment session
When your customer clicks "Pay" on your site, your server creates a payment session.
curl -X POST https://api.catalystpay.com/api/v1/payment-sessions/ \
-H "Authorization: Bearer rk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"campaign_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"lead": {
"first_name": "Max",
"last_name": "Mustermann",
"email": "[email protected]",
"country": "DE",
"address": "Musterstrasse 42",
"city": "Berlin",
"postal_code": "10115"
}
}'
import requests
response = requests.post(
"https://api.catalystpay.com/api/v1/payment-sessions/",
headers={
"Authorization": "Bearer rk_your_api_key",
"Content-Type": "application/json",
},
json={
"campaign_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"lead": {
"first_name": "Max",
"last_name": "Mustermann",
"email": "[email protected]",
"country": "DE",
"address": "Musterstrasse 42",
"city": "Berlin",
"postal_code": "10115",
},
},
)
session = response.json()
payment_url = session["payment_url"]
const response = await fetch(
"https://api.catalystpay.com/api/v1/payment-sessions/",
{
method: "POST",
headers: {
Authorization: "Bearer rk_your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
campaign_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
lead: {
first_name: "Max",
last_name: "Mustermann",
email: "[email protected]",
country: "DE",
address: "Musterstrasse 42",
city: "Berlin",
postal_code: "10115",
},
}),
}
);
const session = await response.json();
const paymentUrl = session.payment_url;
Response:
{
"session_token": "ps_abc123def456ghi789jkl012mno345",
"status": "PENDING",
"payment_url": "https://pay.catalystpay.com/s/ps_abc123def456ghi789jkl012mno345"
}
Step 2: Redirect the customer
Send the customer's browser to the payment_url from the response. The hosted payment page displays your campaign branding, offer details, and a payment form.
# In a Django/Flask view:
from django.shortcuts import redirect
def checkout_view(request):
# ... create session (Step 1) ...
return redirect(session["payment_url"])
// Client-side redirect:
window.location.href = paymentUrl;
Open in a browser:
https://pay.catalystpay.com/s/ps_abc123def456ghi789jkl012mno345
The customer sees the hosted payment page. They enter their IBAN and account holder name, then submit. CatalystPay handles validation, payment submission, and any gateway-specific verification steps.
After a successful payment, the customer is redirected to the success_redirect_url configured on your hosted page. If the payment fails after all retry attempts, they go to the failure_redirect_url.
Step 3: Receive the webhook
When the payment completes, CatalystPay sends a payment_session.completed webhook to your configured endpoint.
The webhook arrives as a POST request with these headers:
Content-Type: application/json
X-CatalystPay-Event: payment_session.completed
X-CatalystPay-Signature: a3f2b1c4d5e6...
The payload contains the fields you mapped in your webhook profile. A typical payload looks like:
{
"session_token": "ps_abc123def456ghi789jkl012mno345",
"status": "COMPLETED",
"order_number": "ORD-a1b2c3d4",
"amount": "29.99",
"currency": "EUR",
"email": "[email protected]",
"customer_name": "Max Mustermann"
}
Step 4: Verify the webhook signature
Before processing the webhook, verify the HMAC-SHA256 signature to confirm it came from CatalystPay.
import hmac
import hashlib
import json
def verify_webhook(payload_body: bytes, signature: str, signing_secret: str) -> bool:
# Reconstruct canonical JSON (sorted keys, compact separators)
payload_dict = json.loads(payload_body)
canonical = json.dumps(payload_dict, sort_keys=True, separators=(",", ":"))
expected = hmac.new(
signing_secret.encode("utf-8"),
canonical.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)
const crypto = require("crypto");
// Recursively sort object keys to match Python's sort_keys=True
function sortKeys(obj) {
if (typeof obj !== "object" || obj === null) return obj;
if (Array.isArray(obj)) return obj.map(sortKeys);
return Object.keys(obj).sort().reduce((acc, key) => {
acc[key] = sortKeys(obj[key]);
return acc;
}, {});
}
function verifyWebhook(payloadBody, signature, signingSecret) {
// Reconstruct canonical JSON (sorted keys, compact separators)
const payload = JSON.parse(payloadBody);
const canonical = JSON.stringify(sortKeys(payload));
const expected = crypto
.createHmac("sha256", signingSecret)
.update(canonical)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
# Manual check using openssl:
echo -n '{"amount":"29.99","currency":"EUR","email":"[email protected]"}' \
| openssl dgst -sha256 -hmac "your_signing_secret"
Always verify the signature before processing webhook data. See the Webhooks guide for full details on the signing algorithm.
Step 5: Confirm the session status
As a final check, retrieve the session to confirm the status matches the webhook.
curl https://api.catalystpay.com/api/v1/payment-sessions/ps_abc123def456ghi789jkl012mno345/
response = requests.get(
"https://api.catalystpay.com/api/v1/payment-sessions/ps_abc123def456ghi789jkl012mno345/"
)
print(response.json()["status"]) # "COMPLETED"
const res = await fetch(
"https://api.catalystpay.com/api/v1/payment-sessions/ps_abc123def456ghi789jkl012mno345/"
);
const data = await res.json();
console.log(data.status); // "COMPLETED"
The status field reads "COMPLETED". Your HPP integration is working end-to-end.
What you learned
- Your server creates sessions; CatalystPay handles the payment UI
- The
payment_urlin the response is where customers complete payment - Webhooks notify your server when payment completes
- Always verify the
X-CatalystPay-Signaturebefore processing webhooks
Next steps
- Server-to-Server Integration -- Build your own payment form instead of using the HPP
- Sandbox Testing -- Test decline flows and webhook verification
- Webhooks -- Full webhook reference with all event types