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_token and status: "COMPLETED"

Expected: One webhook received with the session token matching Step 1.

Step 4: Confirm via API

curl https://api.staging.catalystpay.com/api/v1/payment-sessions/ps_your_session_token/

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:

  • status is "declined" with retry_allowed: true (retries remain; "failed" would mean no retries left)
  • error contains 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-Signature header 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