Sandbox Testing
Walk through test scenarios step-by-step in the staging environment to verify your integration before going live.
Prerequisites
- A staging API key (
rk_...) -- separate from your production key - Staging base URL:
https://api.staging.catalystpay.com/api/v1 - A campaign ID configured in the staging environment
- A publicly accessible webhook endpoint (use webhook.site for quick testing)
The staging environment connects to gateway sandboxes. No real money is moved. Your staging API key and credentials are separate from production.
Test IBANs
Use these IBANs in the staging environment to trigger specific outcomes.
| IBAN | Outcome | Use for |
|---|---|---|
DE89370400440532013000 |
Approved | Happy path testing |
DE91100000000123456789 |
Declined | Decline handling and retry flow |
GB82WEST12345698765432 |
Approved | Non-German IBAN acceptance |
Scenario 1: Happy path (HPP flow)
Create a session, complete payment on the hosted page, and verify the webhook.
Step 1: Create a session
curl -X POST https://api.staging.catalystpay.com/api/v1/payment-sessions/ \
-H "Authorization: Bearer rk_your_staging_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"
}
}'
Expected: 201 Created with session_token, status: "PENDING", and payment_url.
Step 2: Complete payment
Open the payment_url in a browser. Enter:
- IBAN:
DE89370400440532013000 - Account holder:
Max Mustermann
Submit the form.
Expected: The page redirects to the campaign's success_redirect_url. If the gateway requires verification, you are redirected to a verification page first.
Step 3: Check the webhook
Go to your webhook endpoint (e.g., webhook.site) and look for a POST request with:
- Header
X-CatalystPay-Event: payment_session.completed - Header
X-CatalystPay-Signature(HMAC-SHA256 hex digest) - JSON body containing your
session_tokenandstatus: "COMPLETED"
Expected: One webhook received with the session token matching Step 1.
Step 4: Confirm via API
Expected: status is "COMPLETED".
Scenario 2: S2S decline and retry
Submit a payment that gets declined, then retry with a valid IBAN.
Step 1: Create a session
curl -X POST https://api.staging.catalystpay.com/api/v1/payment-sessions/ \
-H "Authorization: Bearer rk_your_staging_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"
}
}'
Save the session_token.
Step 2: Submit with a decline IBAN
curl -X POST https://api.staging.catalystpay.com/api/v1/payment-sessions/{token}/pay/ \
-H "Content-Type: application/json" \
-d '{
"iban": "DE91100000000123456789",
"iban_holder_name": "Max Mustermann"
}'
Expected response:
{
"session_token": "ps_...",
"status": "declined",
"verification_url": null,
"error": "Payment declined by bank",
"retry_allowed": true
}
Key things to verify:
statusis"declined"withretry_allowed: true(retries remain;"failed"would mean no retries left)errorcontains a decline reason- No webhook is sent for declines
Step 3: Retry with a valid IBAN
curl -X POST https://api.staging.catalystpay.com/api/v1/payment-sessions/{token}/pay/ \
-H "Content-Type: application/json" \
-d '{
"iban": "DE89370400440532013000",
"iban_holder_name": "Max Mustermann"
}'
Expected: status is "completed". Now a webhook fires.
Scenario 3: Webhook signature verification
Verify that your code correctly validates HMAC-SHA256 signatures.
Step 1: Trigger a webhook
Complete a successful payment using the steps from Scenario 1.
Step 2: Capture the webhook data
From your test endpoint (webhook.site), copy:
- The raw JSON payload body
- The
X-CatalystPay-Signatureheader value
Step 3: Verify the signature locally
import hmac
import hashlib
import json
# Paste your values here
signing_secret = "your_webhook_profile_signing_secret"
payload_body = '{"session_token":"ps_...","status":"COMPLETED","order_number":"ORD-..."}'
received_signature = "a3f2b1c4d5e6..."
# Reconstruct canonical form (sorted keys, compact separators)
payload_dict = json.loads(payload_body)
canonical = json.dumps(payload_dict, sort_keys=True, separators=(",", ":"))
computed = hmac.new(
signing_secret.encode("utf-8"),
canonical.encode("utf-8"),
hashlib.sha256,
).hexdigest()
if hmac.compare_digest(computed, received_signature):
print("Signature valid")
else:
print("Signature INVALID -- check signing secret and payload")
const crypto = require("crypto");
// Paste your values here
const signingSecret = "your_webhook_profile_signing_secret";
const payloadBody =
'{"session_token":"ps_...","status":"COMPLETED","order_number":"ORD-..."}';
const receivedSignature = "a3f2b1c4d5e6...";
// Reconstruct canonical form (sorted keys)
const payload = JSON.parse(payloadBody);
const sorted = Object.keys(payload)
.sort()
.reduce((obj, key) => ({ ...obj, [key]: payload[key] }), {});
const canonical = JSON.stringify(sorted);
const computed = crypto
.createHmac("sha256", signingSecret)
.update(canonical)
.digest("hex");
if (
crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(receivedSignature))
) {
console.log("Signature valid");
} else {
console.log("Signature INVALID -- check signing secret and payload");
}
# Compute the expected signature:
echo -n '{"amount":"29.99","currency":"EUR","email":"[email protected]","order_number":"ORD-a1b2c3d4","session_token":"ps_...","status":"COMPLETED"}' \
| openssl dgst -sha256 -hmac "your_webhook_profile_signing_secret"
Expected: The computed signature matches the received X-CatalystPay-Signature value.
The signature is computed over the canonical JSON (sorted keys, compact separators: "," and ":"). If you re-serialize with different formatting, the signature will not match.
Scenario 4: IBAN validation
Test the IBAN validation endpoint with valid and invalid inputs.
Valid IBAN
curl -X POST https://api.staging.catalystpay.com/api/v1/iban-validate/ \
-H "Authorization: Bearer rk_your_staging_key" \
-H "Content-Type: application/json" \
-d '{"iban": "DE89370400440532013000"}'
Expected:
{
"valid": true,
"bank_name": "Commerzbank",
"bic": "COBADEFFXXX"
}
Invalid IBAN
curl -X POST https://api.staging.catalystpay.com/api/v1/iban-validate/ \
-H "Authorization: Bearer rk_your_staging_key" \
-H "Content-Type: application/json" \
-d '{"iban": "DE00000000000000000000"}'
Expected:
{
"valid": false,
"error": "Invalid IBAN checksum"
}
Scenario 5: Session cancellation
Cancel a session before payment is submitted.
Step 1: Create a session
curl -X POST https://api.staging.catalystpay.com/api/v1/payment-sessions/ \
-H "Authorization: Bearer rk_your_staging_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"
}
}'
Step 2: Cancel the session
curl -X POST https://api.staging.catalystpay.com/api/v1/payment-sessions/{token}/cancel/ \
-H "Authorization: Bearer rk_your_staging_key"
Expected:
{
"session_token": "ps_...",
"status": "EXPIRED"
}
Step 3: Verify payment is blocked
Try to submit a payment on the cancelled session:
curl -X POST https://api.staging.catalystpay.com/api/v1/payment-sessions/{token}/pay/ \
-H "Content-Type: application/json" \
-d '{
"iban": "DE89370400440532013000",
"iban_holder_name": "Max Mustermann"
}'
Expected: 410 Gone with "Session expired or completed". No payment is processed.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
401 on create session |
Wrong API key or missing Bearer prefix |
Verify key starts with rk_ and header is Authorization: Bearer rk_... |
404 on session endpoints |
Incorrect token or session expired | Check the token; create a new session if needed |
409 on create session |
Lead already has an active session | Cancel the existing session first, or use a different email |
| No webhook received | Endpoint not reachable or not configured | Confirm URL is publicly accessible; check webhook profile in admin |
| Signature mismatch | Wrong secret or different JSON serialization | Use sorted keys and compact separators: sort_keys=True, separators=(",", ":") |
Next steps
- Getting Started -- Quick-start if you have not used the API yet
- HPP Integration -- Full hosted payment page tutorial
- S2S Integration -- Full server-to-server tutorial
- Webhooks -- Webhook event types and signature verification