Table of contents

Troubleshooting

Precium S2S Troubleshooting Guide

This guide helps you diagnose and resolve common issues when integrating with the Precium S2S API.

Diagnostic Decision Tree

Use this flowchart to identify your issue category:

flowchart TD
    A[Issue Encountered] --> B{Error in Response?}
    B -->|Yes| C{HTTP Status?}
    B -->|No| D{Unexpected Behavior?}
    C -->|4xx| E[Client Error]
    C -->|5xx| F[Server Error]
    E --> G{Status Code}
    G -->|400| H[Bad Request - Check payload]
    G -->|401| I[Auth Error - Check API key]
    G -->|403| J[Forbidden - Check permissions]
    G -->|404| K[Not Found - Check endpoint/ID]
    G -->|422| L[Validation - Check data format]
    F --> M{Is it intermittent?}
    M -->|Yes| N[Retry with backoff]
    M -->|No| O[Contact support]
    D -->|3DS not completing| P[3DS Issues]
    D -->|Webhooks not arriving| Q[Webhook Issues]
    D -->|Charges failing| R[Payment Issues]

Authentication Issues

Issue: "401 Unauthorized" Response

Symptoms:

JSON

{
 "error": "Invalid or missing API key"
}

Solutions:

|Cause|Solution| |---|---| |Missing Authorization header|Add `Authorization: Bearer ` to request| |Using wrong key type|Use Standard key for most endpoints, S2S key for direct post| |API key has whitespace|Trim whitespace from key| |API key expired or revoked|Generate new key in dashboard| |Using test key in production|Switch to live keys|

Verification:

BASH

curl -X GET https://gate.reviopay.com/api/v1/clients/ \
 -H "Authorization: Bearer YOUR_API_KEY" \
 -H "Accept: application/json"

Issue: "403 Forbidden" Response

Common Causes:

  • S2S not enabled for your account
  • Attempting operations outside your permission scope
  • IP restrictions in place

Solution: Contact Precium support to verify your account permissions.

Request Validation Issues

Issue: "400 Bad Request" or "422 Unprocessable Entity"

Check these common mistakes:

Missing Required Fields

JSON

// Wrong
{
 "currency": "ZAR",
 "products": [{"name": "Test", "price": 100}]
}

// Correct
{
 "client_id": "uuid-here",
 "brand_id": "your-brand-id",
 "currency": "ZAR",
 "products": [{"name": "Test", "price": 100}],
 "success_redirect": "https://example.com/success",
 "failure_redirect": "https://example.com/failure"
}

Wrong Amount Format

JAVASCRIPT

// Wrong - decimal amount
{ "price": 299.00 }

// Correct - cents as integer
{ "price": 29900 }

Invalid Expiry Format

Wrong: 12/2028, 2028-12, 1228
Correct: 12/28 (MM/YY format)

Invalid Currency

Wrong: "Rand", "rand", "R"
Correct: "ZAR" (ISO 4217)

3DS Issues

Issue: 3DS Challenge Not Loading

flowchart TD
    A[3DS Not Loading] --> B{Form submitted correctly?}
    B -->|No| C[Fix form submission]
    B -->|Yes| D{Correct fields included?}
    D -->|No| E[Add MD, PaReq, TermUrl]
    D -->|Yes| F{Correct target URL?}
    F -->|No| G[Use 3ds.3ds_url]
    F -->|Yes| H[Check browser console for errors]

Required 3DS Form Fields:

HTML

<form method="POST" action="{{3ds_url}}">
 <input type="hidden" name="MD" value="{{MD}}" />
 <input type="hidden" name="PaReq" value="{{PaReq}}" />
 <input type="hidden" name="TermUrl" value="{{callback_url}}" />
</form>

Issue: 3DS Callback Not Received

Causes:

  • callback_url not accessible from internet
  • Firewall blocking incoming requests
  • URL mismatch (http vs https)

Solution: Ensure your callback URL is publicly accessible, uses HTTPS in production, and returns HTTP 200 within 5 seconds.

Issue: 3ds_authentication_failed Error

Allow the customer to retry. If persistent with a specific card/bank, investigate with Precium support.

Direct Post Issues

Issue: "Direct Post URL Expired"

Cause: Direct post URLs are single-use and expire after the first submission.

Solution: Create a new purchase to get a fresh direct_post_url.

Issue: Card Data Submission Fails

|Check|Correct Format| |---|---| |Card number|Digits only, no spaces (e.g., `4000000000001091`)| |Expiry|MM/YY format (e.g., `12/28`)| |CVC|3 digits (4 for Amex)| |Content-Type|`application/x-www-form-urlencoded`| |API Key|Must use S2S key, not Standard key|

Working Example:

BASH

curl -X POST "DIRECT_POST_URL" \
 -H "Authorization: Bearer YOUR_S2S_API_KEY" \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "cardholder_name=John Smith" \
 -d "card_number=4000000000001091" \
 -d "expires=12/28" \
 -d "cvc=123" \
 -d "remote_ip=196.21.45.123" \
 -d "user_agent=Mozilla/5.0" \
 -d "accept_header=text/html" \
 -d "language=en" \
 -d "java_enabled=false" \
 -d "javascript_enabled=true" \
 -d "color_depth=24" \
 -d "utc_offset=-120" \
 -d "screen_width=1920" \
 -d "screen_height=1080"

Charge/Payment Issues

Issue: Charge Returns Error

flowchart TD
    A[Charge Failed] --> B{Check error code}
    B --> C{Type?}
    C -->|Validation| D[Fix input data]
    C -->|Auth Failure| E[3DS not completed]
    C -->|Decline| F{Soft or Hard?}
    C -->|Network| G[Retry later]
    F -->|Soft| H[Can retry with same card]
    F -->|Hard| I[Request different card]
    E --> J[Complete 3DS first]
    D --> K[Correct and retry]

Pre-Authorization Issues

Issue: Capture Fails After Authorization

|Error|Cause|Solution| |---|---|---| |`authorization_expired`|Auth held too long|Re-authorize with customer| |`capture_amount_exceeds_authorization`|Capturing more than authed|Reduce capture amount| |`already_captured`|Duplicate capture|Check transaction status| |`already_voided`|Auth was cancelled|Re-authorize|

Diagnostic Steps:

  1. Check purchase status — must be "authorized"
  2. Verify capture amount ≤ authorized amount
  3. Check authorization age (7-30 days typical expiry)

PYTHON

purchase = client.get_purchase(purchase_id)
if purchase['status'] != 'authorized':
   print(f"Cannot capture - status is: {purchase['status']}")

Issue: Partial Capture Not Working

Error: partial_capture_not_supported

Solution:

  • Capture the full amount and refund the difference, OR
  • Void and create a new authorization for the correct amount
  • Contact Precium to enable partial capture

Issue: Pre-Authorization Void Fails

PYTHON

purchase = client.get_purchase(purchase_id)
if purchase['status'] == 'paid':
   # Already captured - use refund
   client.refund_purchase(purchase_id)
elif purchase['status'] == 'authorized':
   # Can void
   client.void_purchase(purchase_id)

Zero Authorization Issues

Issue: Zero Auth Not Permitted

Error: zero_auth_not_permitted

Solutions:

  1. Contact Precium to enable zero authorization
  2. Use a minimum amount (e.g., R1.00) and immediately refund
  3. Use network token validation instead

Issue: Zero Auth Doesn't Generate Token

PYTHON

purchase = client.create_zero_auth_purchase(
   client_id=customer_id,
   currency="ZAR",
   success_redirect="https://...",
   failure_redirect="https://...",
   force_recurring=True  # Required for tokenization
)

External 3DS (MPI) Issues

Issue: External 3DS Data Rejected

Error: invalid_external_3ds_data

|Field|Required|Format| |---|---|---| |`is_external_3DS`|Yes|`true`| |`authentication_transaction_id`|Yes|Your MPI's transaction ID| |`cavv`|Yes|Base64 encoded| |`eci_raw`|Yes|"05", "02", "06", "01", etc.| |`xid`|3DS1 only|Base64 encoded| |`ds_trans_id`|3DS2 only|UUID format|

PYTHON

# Wrong - missing is_external_3DS
payment_details = {
   "card": {
       "cavv": "AAA...",
       "eci_raw": "05"
   }
}

# Correct
payment_details = {
   "card": {
       "is_external_3DS": True,  # Required
       "authentication_transaction_id": "YOUR_MPI_TX_ID",
       "cavv": "AAA...",
       "eci_raw": "05",
       "ds_trans_id": "uuid-here"  # For 3DS2
   }
}

Issue: External 3DS Brand Not Configured

Error: external_3ds_not_enabled

Cause: Must use a Non-3DS brand for external MPI. Contact Precium to create a Non-3DS brand.

Network Token Issues

Issue: Invalid Network Token

Error: invalid_network_token

Request fresh token from MDES/VTS and verify token requestor ID is correct.

Issue: Cryptogram Expired

Error: cryptogram_expired

PYTHON

# Request fresh cryptogram immediately before transaction
cryptogram = request_new_cryptogram_from_token_service()
# Submit transaction immediately after

Webhook Issues

Issue: Webhooks Not Arriving

flowchart TD
    A[Webhooks Not Arriving] --> B{URL configured in dashboard?}
    B -->|No| C[Configure webhook URL]
    B -->|Yes| D{Endpoint accessible?}
    D -->|No| E[Check firewall/DNS]
    D -->|Yes| F{SSL valid?}
    F -->|No| G[Fix SSL certificate]
    F -->|Yes| H{Returning 200?}
    H -->|No| I[Fix endpoint code]
    H -->|Yes| J[Check Precium webhook logs]

Verification:

BASH

curl -X POST https://your-webhook-url.com/precium \
 -H "Content-Type: application/json" \
 -d '{"test": true}'

Issue: Webhook Signature Verification Fails

|Cause|Solution| |---|---| |Wrong secret|Copy fresh secret from dashboard| |Parsed JSON instead of raw|Use raw request body for signature| |Encoding issues|Ensure UTF-8 encoding throughout| |Missing timestamp|Include timestamp in signature calculation|

Debugging:

PYTHON

print(f"Received signature: {signature}")
print(f"Timestamp: {timestamp}")
print(f"Raw payload length: {len(payload)}")

message = f"{timestamp}.{payload.decode('utf-8')}"
computed = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()
print(f"Computed signature: {computed}")
print(f"Match: {hmac.compare_digest(signature, computed)}")

Issue: Webhook Timeout Errors

Solution — Async Processing:

PYTHON

# Wrong - synchronous processing
@app.route('/webhook', methods=['POST'])
def webhook():
   data = request.json
   process_payment(data)  # Slow - might timeout
   return '', 200

# Correct - acknowledge immediately, process async
@app.route('/webhook', methods=['POST'])
def webhook():
   if not verify_signature(request):
       return '', 401
   webhook_id = request.headers.get('X-Webhook-ID')
   queue.enqueue(process_webhook, request.json, webhook_id)
   return '', 200  # Return immediately

Issue: Duplicate Webhooks

PYTHON

import redis
redis_client = redis.Redis()

def handle_webhook(data, webhook_id):
   cache_key = f"webhook:{webhook_id}"
   if redis_client.exists(cache_key):
       return  # Already processed
   redis_client.setex(cache_key, 86400, "processing")
   try:
       process_event(data)
       redis_client.setex(cache_key, 86400, "completed")
   except Exception as e:
       redis_client.delete(cache_key)
       raise

Issue: Testing Webhooks Locally

BASH

# Install and start ngrok
ngrok http 3000
# Use the generated URL in Precium dashboard
# e.g., https://abc123.ngrok.io/webhooks/precium

Trigger a test webhook:

BASH

curl -X POST https://gate.reviopay.com/api/v1/webhooks/test/ \
 -H "Authorization: Bearer YOUR_API_KEY" \
 -H "Content-Type: application/json" \
 -d '{
   "event": "purchase.paid",
   "webhook_url": "https://abc123.ngrok.io/webhooks/precium"
 }'

Payment Decline Issues

Issue: "insufficient_funds"

  • Notify the customer of insufficient funds
  • Suggest retry after funding the account
  • Offer alternative payment methods
  • For MIT: Schedule retry per retry logic

Customer Message: "Your payment couldn't be processed due to insufficient funds. Please add funds to your account and try again."

Issue: "do_not_honour"

Potential Causes:

  • Card restrictions
  • Unusual spending pattern
  • Bank's fraud detection
  • Account issues

Actions:

  1. Suggest the customer contact their bank
  2. Offer an alternative payment method
  3. For MIT: Retry per schedule

Issue: Token-Based Charge Fails

Common Causes:

  • Token expired
  • Original card expired/replaced
  • Wrong previous_network_transaction_id
  • Incorrect original_amount_cents

Verification Checklist:

  • Token is from the same client
  • previous_network_transaction_id matches original CIT
  • original_amount_cents matches tokenization amount
  • is_recurring: true in purchase

Issue: Refund Amount Exceeds Original

PYTHON

original_amount = 29900
already_refunded = 5000
max_refundable = original_amount - already_refunded

if requested_refund > max_refundable:
   raise ValueError(f"Can only refund up to {max_refundable}")

Issue: Refund Pending for Days

|Scenario|Timeline| |---|---| |Same-day (pre-settlement)|Void — immediate| |Post-settlement|5-10 business days| |International cards|Up to 15 business days|

Testing Issues

Issue: Test Cards Not Working in Production

Test card numbers only work in the sandbox environment. Use real cards in production.

Issue: Real Cards Not Working in Sandbox

Sandbox doesn't process real cards. Use designated test card numbers for sandbox testing.

Performance Issues

Issue: API Requests Are Slow

Optimisation Checklist:

  1. Use connection pooling
  2. Implement request timeouts
  3. Cache client IDs (don't create new clients each time)
  4. Use webhooks instead of polling for status

PYTHON

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()
retry = Retry(total=3, backoff_factor=0.5)
adapter = HTTPAdapter(max_retries=retry, pool_connections=10, pool_maxsize=10)
session.mount('https://', adapter)

When to Contact Support

Contact Precium support (support@precium.com) when:

  • URGENT priority errors persist
  • Unexpected error codes not in documentation
  • Settlement or reconciliation discrepancies
  • Account configuration issues
  • Suspected fraud or security concerns

Include in Support Request:

  • Purchase ID(s)
  • Timestamp of issue
  • Full error response
  • Steps to reproduce
  • Expected vs actual behaviour

Error Code Quick Lookup

|Error|Action| |---|---| |`insufficient_funds`|Retry later, different card| |`do_not_honour`|Retry later, contact bank| |`expired_card`|New card required| |`3ds_authentication_failed`|Retry authentication| |`acquirer_configuration_error`|Contact support| |`invalid_card_number`|Check card number format| |`antifraud_general`|Do not retry, investigate| |`authorization_expired`|Re-authorize| |`capture_amount_exceeds_authorization`|Reduce capture amount| |`void_not_permitted`|Check transaction status, use refund if settled| |`partial_capture_not_supported`|Capture full amount or void and re-auth| |`refund_amount_exceeds_original`|Check total refunded amount| |`invalid_external_3ds_data`|Verify all required 3DS fields| |`zero_auth_not_permitted`|Contact support to enable| |`invalid_network_token`|Request new token from MDES/VTS|